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()
andfinish()
are simple replacements forshowDialog()
anddismiss()
- 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 passedBundle
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.