Dialogs in Android 1 are used to shows alerts for making decisions or to edit a single value 2.
But there are some differences between an AlertDialog and a BasicDialog.
In an AlertDialog you always want to show a message and at least one Button for user interaction.
In a BasicDialog you have a custom view to a TextView or something more complex.
The first working example was quickly done, thanks to the Android Developers Guide 3.
But I was a little bit afraid of getting a warning in Android Studio and in the Lint Report.
Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout’s root element)
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { LayoutInflater inflater = getActivity().getLayoutInflater(); View view = inflater.inflate(R.layout.fragment_edit_name, null); ButterKnife.inject(this, view);
Help I’m using a BasicDialog, but what went wrong here?
The solution looked fine and suggests to use the AlertDialog.Builder for a BasicDialog, but I think this example is just outdated and we can do better!
And we can, looking into the API-Reference we find a better example 4.
So lets get started and have a look at the „wrong“ implementation first!
Example: Layout Inflation without a Parent“
I made an working example you can look at Github5 where the AlertDialog.Builder is used to build with a custom layout.
package de.avalax.fitbuddy.presentation.dialog; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import butterknife.ButterKnife; import butterknife.InjectView; import de.avalax.fitbuddy.R; public class EditNameDialogFragment extends DialogFragment { private static final String ARGS_NAME = "name"; private static final String ARGS_HINT = "hint"; @InjectView(R.id.nameEditText) protected EditText nameEditText; private DialogListener listener; public static EditNameDialogFragment newInstance(String name, String hint) { EditNameDialogFragment fragment = new EditNameDialogFragment(); Bundle args = new Bundle(); args.putString(ARGS_NAME, name); args.putString(ARGS_HINT, hint); fragment.setArguments(args); return fragment; } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { listener = (DialogListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement EditNameDialogFragment.DialogListener"); } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { LayoutInflater inflater = getActivity().getLayoutInflater(); View view = inflater.inflate(R.layout.fragment_edit_name, null); ButterKnife.inject(this, view); String name = getArguments().getString(ARGS_NAME); nameEditText.setText(name); nameEditText.setHint(getArguments().getString(ARGS_HINT)); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setView(view) .setTitle(R.string.dialog_change_name) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { listener.onDialogPositiveClick(EditNameDialogFragment.this); } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { EditNameDialogFragment.this.getDialog().cancel(); } }); return builder.create(); } public String getName() { return nameEditText.getText().toString(); } public interface DialogListener { public void onDialogPositiveClick(EditNameDialogFragment editNameDialogFragment); } }
In the layout we only have the EditText itself.
<?xml version="1.0" encoding="utf-8"?> <EditText android:id="@+id/nameEditText" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="labelFor" android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="textNoSuggestions" />
And why should we change a working solution?
At first I asked me this question, but there are some reasons why we should change this:
- Make our code more readable
- Move the styling of the dialog to the layout-file
- Remove the warning from the linter
The AlertDialog.Builder is removed and we are using the onCreateView to inflate our layout.
I’m using ButterKnife 6 for view injection and OnClick events.
package de.avalax.dialogfragment; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; public class BasicDialogFragment extends DialogFragment { private static final String ARGS_NAME = "alertText"; @InjectView(R.id.nameEditText) protected EditText nameEditText; private DialogListener listener; public static BasicDialogFragment newInstance(String name) { BasicDialogFragment fragment = new BasicDialogFragment(); Bundle args = new Bundle(); args.putString(ARGS_NAME, name); fragment.setArguments(args); return fragment; } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { listener = (DialogListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement BasicDialogFragment.DialogListener"); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_basic_dialog, container, false); ButterKnife.inject(this, view); getDialog().setTitle(R.string.dialog_change_name); nameEditText.setText(getArguments().getString(ARGS_NAME)); return view; } @OnClick(R.id.done_button) protected void positiveButton() { listener.onDialogPositiveClick(BasicDialogFragment.this); getDialog().dismiss(); } public String getAlertText() { return nameEditText.getText().toString(); } public interface DialogListener { public void onDialogPositiveClick(BasicDialogFragment basicDialogFragment); } }
The Button creation is moved to the layout and uses a custom style. In this step I decided to remove the cancel action.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:minWidth="400dip" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <EditText tools:ignore="labelFor" android:id="@+id/nameEditText" android:layout_width="fill_parent" android:layout_height="0.0dip" android:inputType="textNoSuggestions" android:divider="@null" android:dividerHeight="0.0dip" android:layout_weight="1.0" /> <Button android:id="@+id/done_button" android:layout_gravity="end" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/done_label" style="?android:buttonBarButtonStyle" /> </LinearLayout>
Building the project using Gradle and look at the lint result again and we won’t see any warnings, yay.
Conclusion
Some little changes but we have a great improvement in our source code.
Compared to the old solution, we have a much better cut between the layout and the Java source.
We have moved the labels to the layout and have a method for the OnClick event.
You can grab the source-code for the final version at Github 7.
- http://developer.android.com/guide/topics/ui/dialogs.html
- http://developer.android.com/design/building-blocks/dialogs.html
- http://developer.android.com/guide/topics/ui/dialogs.html#CustomLayout
- http://developer.android.com/reference/android/app/DialogFragment.html
- https://github.com/avalax/dialogfragment/releases/tag/v1.0
- http://jakewharton.github.io/butterknife/
- https://github.com/avalax/dialogfragment/releases/tag/v2.0