Google recommends that we useDialogFragment instead of a simpleDialog by usingFragments API, but it is absurd to use an isolatedDialogFragment for a simple Yes-No confirmation message box. What is the best practice in this case?
- 10In short, among other things, simple
DialogorAlertDialog.Builder::create()::show()will create a dialog that disappears when you rotate the screen.WSBT– WSBT2018-01-08 07:54:38 +00:00CommentedJan 8, 2018 at 7:54
10 Answers10
Yes, useDialogFragment and inonCreateDialog you can simply use an AlertDialog builder anyway to create a simpleAlertDialog with Yes/No confirmation buttons. Not very much code at all.
With regards handling events in your fragment there would be various ways of doing it but I simply define a messageHandler in myFragment, pass it into theDialogFragment via its constructor and then pass messages back to my fragment's handler as approprirate on the various click events. Again various ways of doing that but the following works for me.
In the dialog hold a message and instantiate it in the constructor:
private Message okMessage;...okMessage = handler.obtainMessage(MY_MSG_WHAT, MY_MSG_OK);Implement theonClickListener in your dialog and then call the handler as appropriate:
public void onClick(..... if (which == DialogInterface.BUTTON_POSITIVE) { final Message toSend = Message.obtain(okMessage); toSend.sendToTarget(); } }Edit
And asMessage is parcelable you can save it out inonSaveInstanceState and restore it
outState.putParcelable("okMessage", okMessage);Then inonCreate
if (savedInstanceState != null) { okMessage = savedInstanceState.getParcelable("okMessage");}14 Comments
target which will be null if you load it from a Bundle. If the target of a Message is null, and you usesendToTarget, you will get a NullPointerException - not because the Message is null, but because its target is.You can create generic DialogFragment subclasses like YesNoDialog and OkDialog, and pass in title and message if you use dialogs a lot in your app.
public class YesNoDialog extends DialogFragment{ public static final String ARG_TITLE = "YesNoDialog.Title"; public static final String ARG_MESSAGE = "YesNoDialog.Message"; public YesNoDialog() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Bundle args = getArguments(); String title = args.getString(ARG_TITLE); String message = args.getString(ARG_MESSAGE); return new AlertDialog.Builder(getActivity()) .setTitle(title) .setMessage(message) .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null); } }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, null); } }) .create(); }}Then call it using the following:
DialogFragment dialog = new YesNoDialog(); Bundle args = new Bundle(); args.putString(YesNoDialog.ARG_TITLE, title); args.putString(YesNoDialog.ARG_MESSAGE, message); dialog.setArguments(args); dialog.setTargetFragment(this, YES_NO_CALL); dialog.show(getFragmentManager(), "tag");And handle the result inonActivityResult.
12 Comments
YES_NO_CALL,getFragmentManager() andonActivityResult ?YES_NO_CALL is a custom int that is the request code.getFragmentManager() gets the fragment manager for the activity, andonActivityResult() is a fragment lifecycle callback method.Use DialogFragment over AlertDialog:
Since the introduction of API level 13:
theshowDialog method from Activity isdeprecated.Invoking a dialog elsewhere in code is not advisable since you will have to manage the the dialog yourself (e.g. orientation change).
Difference DialogFragment - AlertDialog
Are they so much different? From Android reference regardingDialogFragment:
A DialogFragment is a fragment that displays a dialog window, floating on top of its activity's window. This fragment contains a Dialog object, which it displays as appropriate based on the fragment's state. Control of the dialog (deciding when to show, hide, dismiss it) should be done through the APIhere, not with direct calls on the dialog.
Other notes
- Fragments are a natural evolution in the Android framework due to the diversity of devices with different screen sizes.
- DialogFragments and Fragments are made available in the support library which makes the class usable in all current used versions of Android.
1 Comment
I would recommend usingDialogFragment.
Sure, creating a "Yes/No" dialog with it is pretty complex considering that it should be rather simple task, but creating a similar dialog box withDialog is surprisingly complicated as well.
(Activity lifecycle makes it complicated - you must letActivity manage the lifecycle of the dialog box - and there is no way to pass custom parameters e.g. the custom message toActivity.showDialog if using API levels under 8)
The nice thing is that you can usually build your own abstraction on top ofDialogFragment pretty easily.
5 Comments
String parameter. When the user clicks "Yes", for example, the dialog calls the Activity's method with parameter "agree". These parameters are specified when showing the dialog, for example AskDialog.ask("Do you agree to these terms?", "agree", "disagree");FragmentManager'sfindFragmentByTag. But yeah, it requires a fair bit of code.Fragmentthis and have yourActivityextends yourInterface. Careful of threading though, you could be popping off interface calls when you don't necessarily want them if your concurrency is not in check. Not sure what this does with memory and circular dependency spaghetti though, would anyone else like to chime in? The other option isMessage/Handler but you still may have concurrency issues.Generic AlertDialogFragment with Builder Pattern
In my project, I already usedAlertDialog.Builder alreadya lot before I found out that it's problematic. However, I did not want to change that much code anywhere in my app. Additionally, I actually am a fan of passingOnClickListeners as anonymous classes where they are needed (that is, when usingsetPositiveButton(),setNegativeButton() etc.) instead of having to implement thousands of callback methods to communicate between a dialog fragment and the holder fragment, which can, in my opinion, lead to very confusing and complex code. Especially, if you have multiple different dialogs in one fragment and then need to distinguish in the callback implementations between which dialog currently being shown.
Therefore, I combined different approaches to create a genericAlertDialogFragment helper class which can be usedexactly likeAlertDialog:
SOLUTION
(PLEASE NOTE that I am using Java 8 lambda expressions in my code, so you might have to change parts of the code if you are not usinglambda expressions yet.)
/** * Helper class for dialog fragments to show a {@link AlertDialog}. It can be used almost exactly * like a {@link AlertDialog.Builder} * <p /> * Creation Date: 22.03.16 * * @author felix, http://flx-apps.com/ */public class AlertDialogFragment extends DialogFragment { protected FragmentActivity activity; protected Bundle args; protected String tag = AlertDialogFragment.class.getSimpleName(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activity = getActivity(); args = getArguments(); } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Dialog dialog = setDialogDefaults(new AlertDialog.Builder(getActivity())).create(); if (args.containsKey("gravity")) { dialog.getWindow().getAttributes().gravity = args.getInt("gravity"); } dialog.setOnShowListener(d -> { if (dialog != null && dialog.findViewById((android.R.id.message)) != null) { ((TextView) dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } }); return dialog; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); if (args.containsKey("onDismissListener")) { Parcelable onDismissListener = args.getParcelable("onDismissListener"); if (onDismissListener != null && onDismissListener instanceof ParcelableOnDismissListener) { ((ParcelableOnDismissListener) onDismissListener).onDismiss(this); } } } /** * Sets default dialog properties by arguments which were set using {@link #builder(FragmentActivity)} */ protected AlertDialog.Builder setDialogDefaults(AlertDialog.Builder builder) { args = getArguments(); activity = getActivity(); if (args.containsKey("title")) { builder.setTitle(args.getCharSequence("title")); } if (args.containsKey("message")) { CharSequence message = args.getCharSequence("message"); builder.setMessage(message); } if (args.containsKey("viewId")) { builder.setView(getActivity().getLayoutInflater().inflate(args.getInt("viewId"), null)); } if (args.containsKey("positiveButtonText")) { builder.setPositiveButton(args.getCharSequence("positiveButtonText"), (dialog, which) -> { onButtonClicked("positiveButtonListener", which); }); } if (args.containsKey("negativeButtonText")) { builder.setNegativeButton(args.getCharSequence("negativeButtonText"), (dialog, which) -> { onButtonClicked("negativeButtonListener", which); }); } if (args.containsKey("neutralButtonText")) { builder.setNeutralButton(args.getCharSequence("neutralButtonText"), (dialog, which) -> { onButtonClicked("neutralButtonListener", which); }); } if (args.containsKey("items")) { builder.setItems(args.getStringArray("items"), (dialog, which) -> { onButtonClicked("itemClickListener", which); }); } // @formatter:off // FIXME this a pretty hacky workaround: we don't want to show the dialog if onClickListener of one of the dialog's button click listener were lost // the problem is, that there is no (known) solution for parceling a OnClickListener in the long term (only for state changes like orientation change, // but not if the Activity was completely lost) if ( (args.getParcelable("positiveButtonListener") != null && !(args.getParcelable("positiveButtonListener") instanceof ParcelableOnClickListener)) || (args.getParcelable("negativeButtonListener") != null && !(args.getParcelable("negativeButtonListener") instanceof ParcelableOnClickListener)) || (args.getParcelable("neutralButtonListener") != null && !(args.getParcelable("neutralButtonListener") instanceof ParcelableOnClickListener)) || (args.getParcelable("itemClickListener") != null && !(args.getParcelable("itemClickListener") instanceof ParcelableOnClickListener)) ) { new DebugMessage("Forgot onClickListener. Needs to be dismissed.") .logLevel(DebugMessage.LogLevel.VERBOSE) .show(); try { dismissAllowingStateLoss(); } catch (NullPointerException | IllegalStateException ignored) {} } // @formatter:on return builder; } public interface OnDismissListener { void onDismiss(AlertDialogFragment dialogFragment); } public interface OnClickListener { void onClick(AlertDialogFragment dialogFragment, int which); } protected void onButtonClicked(String buttonKey, int which) { ParcelableOnClickListener parcelableOnClickListener = getArguments().getParcelable(buttonKey); if (parcelableOnClickListener != null) { parcelableOnClickListener.onClick(this, which); } } // region Convenience Builder Pattern class almost similar to AlertDialog.Builder // ============================================================================================= public AlertDialogFragment builder(FragmentActivity activity) { this.activity = activity; this.args = new Bundle(); return this; } public AlertDialogFragment addArguments(Bundle bundle) { args.putAll(bundle); return this; } public AlertDialogFragment setTitle(int titleStringId) { return setTitle(activity.getString(titleStringId)); } public AlertDialogFragment setTitle(CharSequence title) { args.putCharSequence("title", title); return this; } public AlertDialogFragment setMessage(int messageStringId) { return setMessage(activity.getString(messageStringId)); } public AlertDialogFragment setMessage(CharSequence message) { args.putCharSequence("message", message); return this; } public AlertDialogFragment setPositiveButton(int textStringId, OnClickListener onClickListener) { return setPositiveButton(activity.getString(textStringId), onClickListener); } public AlertDialogFragment setPositiveButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) { args.putCharSequence("positiveButtonText", text); args.putParcelable("positiveButtonListener", createParcelableOnClickListener(onClickListener)); return this; } public AlertDialogFragment setNegativeButton(int textStringId, AlertDialogFragment.OnClickListener onClickListener) { return setNegativeButton(activity.getString(textStringId), onClickListener); } public AlertDialogFragment setNegativeButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) { args.putCharSequence("negativeButtonText", text); args.putParcelable("negativeButtonListener", createParcelableOnClickListener(onClickListener)); return this; } public AlertDialogFragment setNeutralButton(int textStringId, AlertDialogFragment.OnClickListener onClickListener) { return setNeutralButton(activity.getString(textStringId), onClickListener); } public AlertDialogFragment setNeutralButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) { args.putCharSequence("neutralButtonText", text); args.putParcelable("neutralButtonListener", createParcelableOnClickListener(onClickListener)); return this; } public AlertDialogFragment setOnDismissListener(OnDismissListener onDismissListener) { if (onDismissListener == null) { return this; } Parcelable p = new ParcelableOnDismissListener() { @Override public void onDismiss(AlertDialogFragment dialogFragment) { onDismissListener.onDismiss(dialogFragment); } }; args.putParcelable("onDismissListener", p); return this; } public AlertDialogFragment setItems(String[] items, AlertDialogFragment.OnClickListener onClickListener) { args.putStringArray("items", items); args.putParcelable("itemClickListener", createParcelableOnClickListener(onClickListener)); return this; } public AlertDialogFragment setView(int viewId) { args.putInt("viewId", viewId); return this; } public AlertDialogFragment setGravity(int gravity) { args.putInt("gravity", gravity); return this; } public AlertDialogFragment setTag(String tag) { this.tag = tag; return this; } public AlertDialogFragment create() { setArguments(args); return AlertDialogFragment.this; } public AlertDialogFragment show() { create(); try { super.show(activity.getSupportFragmentManager(), tag); } catch (IllegalStateException e1) { /** * this whole part is used in order to attempt to show the dialog if an * {@link IllegalStateException} was thrown (it's kinda comparable to * {@link FragmentTransaction#commitAllowingStateLoss()} * So you can remove all those dirty hacks if you are sure that you are always * properly showing dialogs in the right moments */ new DebugMessage("got IllegalStateException attempting to show dialog. trying to hack around.") .logLevel(DebugMessage.LogLevel.WARN) .exception(e1) .show(); try { Field mShownByMe = DialogFragment.class.getDeclaredField("mShownByMe"); mShownByMe.setAccessible(true); mShownByMe.set(this, true); Field mDismissed = DialogFragment.class.getDeclaredField("mDismissed"); mDismissed.setAccessible(true); mDismissed.set(this, false); } catch (Exception e2) { new DebugMessage("error while showing dialog") .exception(e2) .logLevel(DebugMessage.LogLevel.ERROR) .show(); } FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction(); transaction.add(this, tag); transaction.commitAllowingStateLoss(); // FIXME hacky and unpredictable workaround } return AlertDialogFragment.this; } @Override public int show(FragmentTransaction transaction, String tag) { throw new NoSuchMethodError("Please use AlertDialogFragment.show()!"); } @Override public void show(FragmentManager manager, String tag) { throw new NoSuchMethodError("Please use AlertDialogFragment.show()!"); } protected ParcelableOnClickListener createParcelableOnClickListener(AlertDialogFragment.OnClickListener onClickListener) { if (onClickListener == null) { return null; } return new ParcelableOnClickListener() { @Override public void onClick(AlertDialogFragment dialogFragment, int which) { onClickListener.onClick(dialogFragment, which); } }; } /** * Parcelable OnClickListener (can be remembered on screen rotation) */ public abstract static class ParcelableOnClickListener extends ResultReceiver implements AlertDialogFragment.OnClickListener { public static final Creator<ResultReceiver> CREATOR = ResultReceiver.CREATOR; ParcelableOnClickListener() { super(null); } @Override public abstract void onClick(AlertDialogFragment dialogFragment, int which); } /** * Parcelable OnDismissListener (can be remembered on screen rotation) */ public abstract static class ParcelableOnDismissListener extends ResultReceiver implements AlertDialogFragment.OnDismissListener { public static final Creator<ResultReceiver> CREATOR = ResultReceiver.CREATOR; ParcelableOnDismissListener() { super(null); } @Override public abstract void onDismiss(AlertDialogFragment dialogFragment); } // ============================================================================================= // endregion}USAGE
// showing a normal alert dialog with state loss on configuration changes (like device rotation)new AlertDialog.Builder(getActivity()) .setTitle("Are you sure? (1)") .setMessage("Do you really want to do this?") .setPositiveButton("Yes", (dialog, which) -> Toast.makeText(getContext(), "Yes clicked", Toast.LENGTH_SHORT).show()) .setNegativeButton("Cancel", null) .show();// showing a dialog fragment using the helper class with no state loss on configuration changesnew AlertDialogFragment.builder(getActivity()) .setTitle("Are you sure? (2)") .setMessage("Do you really want to do this?") .setPositiveButton("Yes", (dialog, which) -> Toast.makeText(getContext(), "Yes clicked", Toast.LENGTH_SHORT).show()) .setNegativeButton("Cancel", null) .show();I am posting this here not only to share my solution, but also because I wanted to ask you people for your opinion: Is this approach legit or problematic to some extent?
6 Comments
DialogFragment is basically a Fragment that can be used as a dialog.
Using DialogFragment over Dialog due to the following reasons:
- DialogFragment is automatically re-created after configuration changes and save & restore flow
- DialogFragment inherits full Fragment’s lifecycle
- No more IllegalStateExceptions and leaked window crashes. This was pretty common when the activity was destroyed with the Alert Dialogstill there.
Comments
May I suggest a little simplification of @ashishduh's answer:
public class AlertDialogFragment extends DialogFragment {public static final String ARG_TITLE = "AlertDialog.Title";public static final String ARG_MESSAGE = "AlertDialog.Message";public static void showAlert(String title, String message, Fragment targetFragment) { DialogFragment dialog = new AlertDialogFragment(); Bundle args = new Bundle(); args.putString(ARG_TITLE, title); args.putString(ARG_MESSAGE, message); dialog.setArguments(args); dialog.setTargetFragment(targetFragment, 0); dialog.show(targetFragment.getFragmentManager(), "tag");}public AlertDialogFragment() {}@NonNull@Overridepublic AlertDialog onCreateDialog(Bundle savedInstanceState){ Bundle args = getArguments(); String title = args.getString(ARG_TITLE, ""); String message = args.getString(ARG_MESSAGE, ""); return new AlertDialog.Builder(getActivity()) .setTitle(title) .setMessage(message) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null); } }) .create();}It removes the need for the user (of the class) to be familiar with the internals of the component and makes usage really simple:
AlertDialogFragment.showAlert(title, message, this);P.S. In my case I needed a simple alert dialog so that's what I created. You can apply the approach to a Yes/No or any other type you need.
Comments
Use Dialog for simple yes or no dialogs.
When you need more complex views in which you need get hold of the lifecycle such as oncreate, request permissions, any life cycle override I would use a dialog fragment. Thus you separate the permissions and any other code the dialog needs to operate without having to communicate with the calling activity.
Comments
Dialog: A dialog is a small window that prompts the user to make a decision or enter additional information.
DialogFragment: A DialogFragment is a special fragment subclass that is designed for creating and hosting dialogs. It allows the FragmentManager to manage the state of the dialog and automatically restore the dialog when a configuration change occurs.
Comments
DialogFragment comes with the power of a dialog and a Fragment. Basically all the lifecycle events are managed very well with DialogFragment automatically, like change in screen configuration etc.
Comments
Explore related questions
See similar questions with these tags.








