28

I have a simple AlertDialog that displays a list of some items and upon clicking one of them, the clicked item is passed back to the enclosing Activity. I also want to perform some default handling when the user cancels the dialog (using the back button) - more specifically, I want to pass an empty string to the activity in such case.

However, if I put the dialog in a DialogFragment (from the compatibility package), the OnCancelListener is not called when I close the dialog with the back button. What am I doing wrong?

public class SelectItemDialog extends DialogFragment {

    public interface Callback {
        void onItemSelected(String string);
    }

    private static final String ARG_ITEMS = "items";

    private Callback callback;

    public static SelectItemDialog fromItems(Collection<String> items) {
        SelectItemDialog fragment = new SelectItemDialog();
        fragment.setArguments(newArguments(items));
        return fragment;
    }

    private static Bundle newArguments(Collection<String> items) {
        Bundle arguments = new Bundle();
        arguments.putStringArray(ARG_ITEMS, items.toArray(new String[items.size()]));
        return arguments;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        callback = (Callback) activity;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final String[] items = getArguments().getStringArray(ARG_ITEMS);
        return new AlertDialog.Builder(getActivity())
            .setTitle(R.string.dialog_select_email_title)
            .setItems(items, new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    callback.onItemSelected(items[which]);
                }
            })
            .setOnCancelListener(new OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    // this code is not executed
                    callback.onItemSelected("");
                    throw new RuntimeException("dialog cancelled");
                }
            })
            .create();
    }
}
Natix
  • 14,017
  • 7
  • 54
  • 69

5 Answers5

54

It might have to do with the fact that there is no explicit call to cancel() from your code. The OnCancelListener documentation says:

This will only be called when the dialog is canceled

Which probably needs an explicit cancel() call.

Either make a positive/negative button with a OnClickListener that calls DialogInterface#cancel() or use a OnDismissListener() with an extra check to see if a list item was clicked.

Also, to listen for a back keypress and cancel the dialog, you can set up an OnKeyListener, like outlined in this SO answer

Also, once you have the Dialog set up, it would also be a good idea to use Dialog#setCanceledOnTouchOutside() in case the the user taps outside the Dialog.

Edit: The below part is the easy way to handle cancel events in a DialogFragment.

Since you are using a DialogFragment, this class has a very handy method, DialogFragment#onCancel() which gets called when the DialogFragment is cancelled. Do your logic in there.

DialogFragments are more complex, with a slightly different lifecycle than normal dialogs. Therefore, first check the documentation if you have a certain Dialog-based approach that you are trying to port to a DialogFragment, some methods may exist that allow your new implementation to function properly!

Community
  • 1
  • 1
A--C
  • 36,351
  • 10
  • 106
  • 92
  • 1
    I edited my question. I want the code in the `OnCancelListener` to be called when the dialog is closed with the *back button*. – Natix Feb 09 '13 at 16:40
  • @Natix see [this answer](http://stackoverflow.com/a/7815342/1815485), it's doing the same thing. You have to add an `OnKeyListener` to watch for the back key press. – A--C Feb 09 '13 at 16:42
  • Well, this solution should probably work, but if I create the dialog directly inside an activity without a `DialogFragment`, then the `OnCancelListener` is called - both when the back button is pressed or if the user clicks outside the dialog. I don't understand why the `DialogFragment` changes the behaviour. – Natix Feb 09 '13 at 16:59
  • 4
    @Natix you can try using [the DialogFragment's `onCancel()`](http://developer.android.com/reference/android/app/DialogFragment.html#onCancel(android.content.DialogInterface)). DialogFragments are more complex than regular dialogs. – A--C Feb 09 '13 at 17:08
  • 1
    `DialogFragment#onCancel()` is exactly what I was looking for! It seems that the `DialogFragment`'s methods override the listeners of the contained `AlertDialog`. Similar case is also the `setCancelable()` method. If you integrate this into your answer, I will accept it. – Natix Feb 09 '13 at 20:30
  • @Natix updated my answer, good thing I checked the documentation! – A--C Feb 09 '13 at 20:41
12

If you are using DialogFragment and want to listen back button then use this -

    this.getDialog().setOnKeyListener(new Dialog.OnKeyListener() {
        @Override
        public boolean onKey(DialogInterface dialog, int keyCode,
                KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                if (****) {
                    your logic
                }
                return true;
            }
            return false;
        }
    });
Nikhil Pingle
  • 747
  • 7
  • 11
11

Note: DialogFragment own the Dialog.setOnCancelListener and Dialog.setOnDismissListener callbacks. You must not set them yourself.

To find out about these events, override onCancel(DialogInterface) and onDismiss(DialogInterface).

public class SelectItemDialog extends DialogFragment {

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        //your code hear
        dialog.cancel();
    }
}

And you should remove .setOnCancelListener()

Bao Le
  • 16,643
  • 9
  • 65
  • 68
1

Actually if you want to use DialogFragment, you can never add OnCancelListener or OnDismissListener to it, since the Dialog Fragment owns callbacks to these methods!

You have 3 options here:

1- go with regular dialogs.
2- set your dialog fragment to cancellable(false) and add a cancel button to the dialog.
3- check @Nikhil Pingle answer.

this is from the documentation of the Dialog Fragment

 * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener
 * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
 * Dialog.setOnDismissListener} callbacks.  You must not set them yourself.</em>
 * To find out about these events, override {@link #onCancel(DialogInterface)}
 * and {@link #onDismiss(DialogInterface)}.</p>
Muhammad Alfaifi
  • 5,662
  • 2
  • 19
  • 23
-1

Cancel Listener or Dismiss listener in DialogFragment can achieve by onDismiss

            DialogFragment  newFragment = new DatePickerFragment();
            newFragment.show(getFragmentManager(), "datePicker");
            newFragment.onDismiss(new DialogInterface(){

                @Override
                public void cancel() {
                    // TODO Auto-generated method stub

                }

                @Override
                public void dismiss() {
                    // TODO Auto-generated method stub

                }

            });
Munish Kapoor
  • 3,141
  • 2
  • 26
  • 41
  • 1
    This code will actually cause the dialog to be shown and immediately dismissed because of `DialogFragment.onDismiss(...)` call. User won't even see it. – Maciej Pigulski Nov 05 '14 at 11:22