Sunday, June 17, 2012

Android: Confirmation Dialog


Warning! This technique is suited to Android API Level 8-10 (Gingerbread). For Honeycomb or newer, use DialogFragment.

Problem: my activity knows how to do something (delete a picture), but it needs to ask the user for confirmation first.

Solution: pop a confirmation dialog, whose "yes" button responds by deleting the picture.

Code:
For the very simplest case, where

  • the activity uses no other dialogs
  • the message in the dialog never changes

then two steps are required:

  1. Instead of taking the action, call showDialog.

    private void deletePicture() {
        showDialog(0);
    }
(where 0 is any integer - we don't care because this is the only dialog used by this activity)

  1. override onCreateDialog(int) to return the dialog object. Building the confirmation dialog is a good-sized chunk of code. The following sample create pulls the creation of a Yes/No dialog into a method, so that the defining of the action we want to take on "Yes" is separated from the cruft of creating this simple-case dialog.


    @Override
    protected Dialog onCreateDialog(int id) {
        return createConfirmationDialog("Delete this picture?", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                reallyDeleteThePicture();
            }
        });
    }


    private AlertDialog createConfirmationDialog(String message, DialogInterface.OnClickListener yesAction) {
        return new AlertDialog.Builder(this).setMessage(message)
                .setPositiveButton("Yes", yesAction)
                .setNegativeButton("No", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        dialogInterface.cancel();
                    }
                }).create();
    }

Unfortunately the code that executes on "yes" is obfuscated by Java's painful syntax, and we all bang our heads on our desks and wish for Java 8. Good thing blog posts have color. If you're here to cut and paste, then replace the red message with your text and the green method call with your own code, and you're done.

What is happening here? showDialog triggers a call to onCreateDialog. Here we're ignoring the int argument because we only ever use one dialog in this activity. AlertDialog.Builder is used to create a dialog with two buttons: Yes, which performs the desired action wrapped in an OnClickListener; and No, which closes the dialog and takes no other action. The user can also close the dialog by pressing back.

The onCreateDialog method will be called only once the first time the user activates the deletePicture() functionality. After that, the same dialog object will be reused.

Let's look at what we need to do in case our assumptions are still not valid. This is still just a confirmation dialog, but what if:

  • the activity uses another dialog.

Suddenly the int passed to showDialog and onCreateDialog matters. They match up, so that the one override onCreateDialog can handle construction of two different dialogs.

Code: Perhaps we also need to confirm overwriting some changes. The below example makes use of the createConfirmationDialog method defined above.

    private static final int DIALOG_DELETE_PICTURE = 1;
    private static final int DIALOG_OVERWRITE_PICTURE = 2;

    private void deletePicture() {
        showDialog(DIALOG_DELETE_PICTURE);
    }


    private void savePicture() {
        showDialog(DIALOG_OVERWRITE_PICTURE);
    }


    @Override
    protected Dialog onCreateDialog(int id) {
        switch (id) {
            case DIALOG_DELETE_PICTURE:
                return createConfirmationDialog("Delete this picture?", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        reallyDeletePicture();
                    }
                });
            case DIALOG_OVERWRITE_PICTURE:
                return createConfirmationDialog("Save changes? This will overwrite the original picture.", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        reallySavePicture();
                    }
                });
        }
        throw new IllegalArgumentException("unknown dialog ID: " + id);
    }

This onCreateDialog method will be called up to twice in the activity's lifetime, once per id.

That wasn't too bad. Now what if

  • the message is different each time the dialog pops

Since onCreateDialog is called only once for each dialog ID, there's another hook for customizing the dialog per use. Override onPrepareDialog(int, Dialog).

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
        switch (id) {
            case DIALOG_DELETE_PICTURE:
                ((AlertDialog) dialog).setMessage("delete picture " + pictureDescription + " ?");
                break;
            default:
                super.onPrepareDialog(id, dialog);
        }
    }

These are simple cases for a simple dialog. For anything more complicated, dig around in the user guide.
This isn't pretty code, with the same concern (this simple dialog) scattered around the activity's code. For Honeycomb and higher, this is abstracted into a DialogFragment, which looks like more fun. Too bad my phone is on Gingerbread.

4 comments:

  1. I know this is old post but surely the support library had DialogFragment(s) two months ago. I'm not sure but doing this will probably make Eclipse scream deprecated to your face.

    ReplyDelete
    Replies
    1. Read the fine print at the top. This technique is for Gingerbread or older. There are still plenty of phones out there that aren't using the latest Android version.

      Delete
    2. I did read the fine print. That's why I mentioned the support library http://developer.android.com/tools/extras/support-library.html. Of course you'll still need at least API level 4.

      Delete
    3. oh wow! That's new to me! Thank you very much.

      Delete