0

I have defined a method for my DialogFragment which will pop another (alert) dialog, where I confirm that the user wants to dismiss the DialogFragment. If that is the case, then I call DialogFragment.dismiss(). If not, the dismissal of the DialogFragment should simply be ignored and the user should return to it as was before.

This method (say, confirmCancel()) is used for the 'Cancel' button at the bottom of the DialogFragment. Since I also want this to appear when the user presses the back button, or when they touch outside the DialogFragment, I have set confirmCancel as its onCancelListener (of course, I have also used getDialog().setCanceledOnTouchOutside(true) too).

This is the code for confirmCancel():

public void confirmCancel()
{
    (new AlertDialog.Builder(getActivity())
             .setIcon(R.drawable.ic_baseline_warning_24)
             .setTitle("Discard changes")
             .setMessage("Are you sure you want to discard changes and go back?")
             .setPositiveButton("Yes", ( dialogInterface, i ) -> dismiss())
             .setNegativeButton("No", ( dialogInterface, i ) -> {})
             .show()).setCanceledOnTouchOutside(true);
}

This works almost perfectly, except for the fact that by the time the AlertDialog is shown on screen, the DialogFragment is already dismissed, and the actions taken in the AlertDialog are of no use at all.

So what I need now is a way to 'cancel' the dismissal of the DialogFragment, or a method that is called before the its dismissal. How do I solve this?

P.S.: getDialog().setCancelable(false) is not helpful to me since I do want the dialog to be cancelled; it's just that I want it cancelled conditionally.

Anchith Acharya
  • 369
  • 1
  • 4
  • 16

1 Answers1

1

Neither DialogFragment nor Dialog offer a condition that is checked before a cancel/dismiss event, that you can set. They only offer listeners that are notified after said events have been fired, when the damage has already been done.

I have combed through the source code and have determined their is no way to force it into a state to ignore the first cancel/dismiss call but still allow the listeners to fire so you can catch the event, I have considered reflection to mess with fields, but it got to messy. I also tried forcing exceptions, to create invalid states, but there are no invalid states that would allow the functionality you want.

Here are the two sources.

https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Dialog.java

https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-activity-release/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java

You could play with the containing Activity's Window to handle touch events and send them back to the DialogFragment using a FragmentResultListener, ie the DialogFragment is set to non-cancelable and when the user touches the outside the Activity picks it up and calls setFragmentResult to send it back to the DialogFragment. This may or may not work depending on how dialogs detect/receive outside touch events, does it consume them or let them fall through, when its non-cancalable.

The easiest solution by far is to copy the source code above, probably just the DialogFragment and make your own to solve this problem.

OR OR OR

I'm totally missing the most obvious solution.

avalerio
  • 2,072
  • 1
  • 12
  • 11
  • Thank you. For now I'll indeed stick with setting the Dialog Fragment to non-cancellable, and manually implementing methods for situations such as when the user touches outside the window or presses the back button. But it baffles me that there isn't a much easier solution to this seemingly simple problem. – Anchith Acharya Sep 11 '21 at 13:34