Android Dialog, or is it?

Dave Smith
Dave Smith

We're all familiar with the concept of providing a temporary modal view to the user in an application for the purposes of notification or basic data entry. In the Android framework (as in many computer systems) a class is provided to the developer to create and maintain these views called a Dialog. This class is then further extended for you to make some convenient versions that allow entry of date/time information and make basic list selections (AlertDialog, TimePickerDialog, etc.). These classes work really well, and I would encourage you to leverage them whenever possible.

But what if you want to use the Dialog interface, but you have some custom data entry needs? Android makes it pretty simple to extend the base Dialog class and create custom behavior with a custom layout, and many people would consider this as the next logical option if they can't solve their problem with one of the canned Dialog solutions.

I'm here to tell you today that there is a better way.

The Problem

There are some subtle, but fairly major issues with the custom Dialog classes and state persistence. By design, the Dialog does not have the same functionality available to it as the Activity for life-cycle management. In particular, this includes managing device orientation changes, which (by default) completely breaks down the Activity and rebuilds it for a new configuration in Android. This is exacerbated by the realization that, in many Android devices, rotation is required EXTREMELY often after an entry dialog is shown because the user will want access to the keyboard to enter the data! If you're dialog doesn't rotate, your app is broken to the user.

So what's the issue?  Do some experimentation with this and monitor LogCat some day. As soon as you do an orientation change with an active dialog, you will get an android.View.WindowLeaked exception from the WindowManager. This occurs (usually) because of reference conflict between the new Activity that gets created, and a reference existing to the previous version of the dialog when you try to show it again. This is actually pretty difficult to manage, even with managed dialogs using the Activity (although, here's a hint: dismissing a dialog and destroying it are not the same thing). The canned dialogs available in the framework do not have this problem, which is why I mentioned using them whenever it fits.

The other "problem" with this implementation is that the Dialog class doesn't get a passed Bundle or other framework inside of which to persist data (like anything the user STARTED to do before they rotated) like the Activity does. This means a lot of passing data back to the calling Activity so it can manage the persisted data.

...This is all doable, but inefficient.

The Solution

Short Answer: Use the Dialog Theme for the Activity class. Personally, the only thing that I REALLY like about the dialog class itself is the presentation method. I like how it partially covers and dims the current activity which, conceptually, links it to the Activity in the user's mind. But everything else on the back end is MUCH better handled with an Activity:

  • startActivityForResult() and finish() are simple replacements for showDialog() and dismiss()
  • Data can be passed to and from the modal session using Intents (a great habit to get into anyway)
  • All the state persistence exists in the framework to handle rotation, like onSaveInstanceState() and the passed Bundle

Plus, creating an Activity class isn't any more work than a custom Dialog. Both are going to need the constructor ( or onCreate() ) to call up super and then set the view to a custom XML layout file to set themselves up.

So the only missing piece is the Theme. The one extra thing that you must do when you create an Activity instead of a Dialog is you must declare it in your manifest so the system knows it exists when it comes to things like Intent resolution. Coincidentally, the manifest is also where you would set the Dialog theme.  Take a look at the example below:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  ...>
  <application>
    <activity android:name=".HomeActivity" />
    <activity android:name=".NiceDialog"
              android:theme="@android:style/Theme.Dialog" />
    ...
  </application>
  ...
</manifest>

Here, the Activity NiceDialog has been skinned to look just like a dialog would using the theme attribute. It now will wrap the content tightly and only partially cover the view of the calling Activity. Now the user is none the wiser, and you get all the benefits of Activity life-cycle management from your custom Dialog.