Code Snippets

The snippets include:

 

HackedSpinner

Android will raise an IllegalArgumentException and leaked window exception if a spinner's choice list is on-screen during an orientation change.  We can avoid crashes by sub-classing Spinner and adding a method to expose its onDetachedFromWindow().

 

A simple HackedSpinner class file is here.  To use it, substitute HackedSpinner for Spinner in all XML files.  Then, call cleanlyDetach() from the activity's onPause() or onStop() or the parent view's onSaveInstanceState() (the latter is what we do).  It doesn't hurt to call this even if the pop-up list isn't on the screen; onDetachedFromWindow() will do nothing in this case.

Preferences Database Initialization

You might expect that giving default values in more than one place leads to bugs when they get out of sync, and this happened to us when retrieving values from the preferences database.  You can define a default value in settings.xml when setting up a PreferenceActivity, and also supply one when getting a key.  We use two methods to combat the problem.  The first is to set up a reference string or boolean value, and to use it in place of a hard-coded value.  (Booleans are used in CheckBoxPreferences, strings in all others, and you'll need to convert numbers with Double.parseDouble() or Integer.parseInt().)  In XML this might look like

 

    <ListPreference

       android:key="@string/prefs_range_key"

       android:defaultValue="@string/prefs_range_dflt" />

 

and in code

 

    res = context.getResources();

    prefs = PreferenceManager.getDefaultSharedPreferences(context);

    prefkey = res.getString(R.string.prefs_range_key);

    dlftval = res.getString(R.string.prefs_range_dflt);

    dist_max = Double.parseDouble(prefs.getString(prefkey, dfltval);

 

Note that we're also storing the key strings in the reference database (in fact, our settings.xml has no hard-coded values in it).

 

The second method is to parse the XML when the application starts and insert default values for all the keys.  This guarantees that nothing is missing - every key will have a value stored.  A PreferenceActivity provides the UI for changing settings, and we add initializePrefs() that's meant to be called when the main activity starts, in onCreate() before inflating the layout.  Sample code is here.  The new function reads settings.xml (change the reference ID if you use a different file).  For each element, it picks out the reference ID for the key name and default value, and then depending on the type of preference, inserts the default as a string or boolean into the database.  It can handle the simple preference types - CheckBoxPreference, EditTextPreference, ListPreference, and RingtonePreference.  Any other will generate a RuntimeException (but, only XML elements with a key and default value are checked).  If you worry about the cost of parsing a file every time the application starts, then add a flag to the database to signal when initialization has been done - it's only needed once.

Dialog Contents Framework

In the Field Notebook application we primarily use dialogs to drive the UI, and this has turned out to be a nice decision.  The 4000 Footers app uses fragments and activities for the separate screens, which adds a lot more overhead for saving and restoring state when switching and on orientation changes.  We're glad to have made the switch (even if dialog's are deprecated), as it made testing and debug much easier.

 

We've adopted a framework for most dialogs that has a standard set-up/interface in the activity's onCreateDialog(), and can preserve its contents.  Each dialog gets a widget that extends FrameLayout.  We assign a unique ID to the frame and set it as the contents of the dialog.  Inside the frame we place an XML layout.  To make sure we start each dialog clean, we use removeDialog() when done, not dismissDialog().  This forces it to be re-created the next time.  The extra cost to doing this seems acceptable compared to having to worry about cleaning up previous contents and listeners.  We bind an onShow listener that populates the widgets inside the frame, and starts the help system (since we know that the dialog's layout is done at this point).  By using onShow instead of onPrepareDialog() we don't have to store a pointer to the dialog's contents.

 

The framework defines four public methods in addition to the constructor: getContents() to return the view to insert in the dialog, fillinFields() for populating the frame when it's shown, accept() if the user wants to carry out the dialog (store a note or take some action), and cancel() if he doesn't.

 

Internally we override the frame's onSaveInstanceState() and onRestoreInstanceState() to preserve what the user has done.  These call an internal SavedState class with four standard methods.  It holds local copies of the outer class's variables so we can keep a separate copy - this is sometimes useful if we want fillinFields() to take data from the restored values and not from the main activity.  For example, from the main activity the note definition dialog will load what's in the database, but if the user hasn't accepted the changes yet, then we want the values to come from the restored state.  The method copyInState() makes the backup (ie. copies from the outer class to the inner), and copyOutState() restores it (inner to outer).  writeToBundle() saves the inner state to the bundle that onSaveInstanceState() creates, and extractFromBundle() pulls it from the restored bundle.  So, when saving the sequence is copyInState(), writeToBundle(), and when restoring extractFromBundle(), copyOutState().

 

We've chosen to use a Bundle for the saved state.  You can also set up a Parcelable, which is a bit more work because the inner class needs to extend BaseSavedState and you need to handle serialization yourself.  We did this originally and it worked, but there was a rare corner case where unpacking the parcel failed and caused the app to crash.  We're not sure this wasn't another weird bug that involves the debugger and zombie apps, but switching to the bundle fixed it and there's no chance the user will see a problem.

 

The framework file is here.  There's a long comment at the top showing how to set up the activity's onCreateDialog() to use it.

Feedback

If you have questions or comments about the code, contact us.