This class was deprecated in API level 28. Use the Support Library DialogFragment for consistent behavior across all devices and access to Lifecycle. A fragment that displays a dialog window, floating on top of its activity's window.
DialogFragment is a utility class which extends the Fragment class. It is a part of the v4 support library and is used to display an overlay modal window within an activity that floats on top of the rest of the content. Essentially a DialogFragment displays a Dialog but inside a Fragment.
AlertDialog is a lightweight version of a Dialog. This is supposed to deal with INFORMATIVE matters only, That's the reason why complex interactions with the user are limited. Dialog on the other hand is able to do even more complex things .
Dialog has an isShowing() method that should return if the dialog is currently visible. So you can use that to see if a dialog is showing and hide it with dismissDialog(). You just have to keep a reference to the Dialogs you create in onCreateDialog().
Yes, use DialogFragment
and in onCreateDialog
you can simply use an AlertDialog builder anyway to create a simple AlertDialog
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 message Handler
in my Fragment
, pass it into the DialogFragment
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 the onClickListener
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 as Message
is parcelable you can save it out in onSaveInstanceState
and restore it
outState.putParcelable("okMessage", okMessage);
Then in onCreate
if (savedInstanceState != null) {
okMessage = savedInstanceState.getParcelable("okMessage");
}
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 in onActivityResult
.
Since the introduction of API level 13:
the showDialog method from Activity is deprecated. 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 regarding DialogFragment:
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 API here, not with direct calls on the dialog.
Other notes
I would recommend using DialogFragment
.
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 with Dialog
is surprisingly complicated as well.
(Activity lifecycle makes it complicated - you must let Activity
manage the lifecycle of the dialog box - and there is no way to pass custom parameters e.g. the custom message to Activity.showDialog
if using API levels under 8)
The nice thing is that you can usually build your own abstraction on top of DialogFragment
pretty easily.
In my project, I already used AlertDialog.Builder
already a 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 passing OnClickListeners
as anonymous classes where they are needed (that is, when using setPositiveButton()
, 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 generic AlertDialogFragment
helper class which can be used exactly like AlertDialog
:
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 using lambda 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 changes
new 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?
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
@Override
public 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.
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 Dialog still there.
More detail
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With