Wednesday 8 September 2010

Bad Sample Is Bad

If you look at Google's Note Pad sample application (and code based on it), you will find a pattern that goes something like this:

\samples\NotePad\src\com\example\android\notepad\NoteEditor.java:109

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final Intent intent = getIntent();
    final String action = intent.getAction();
    if (Intent.ACTION_EDIT.equals(action)) {
        mState = STATE_EDIT;
        mUri = intent.getData();
    } else if (Intent.ACTION_INSERT.equals(action)) {
        mState = STATE_INSERT;
        mUri = getContentResolver().insert(intent.getData(), null);
    }
    ...

This is bad and wrong.

The idea is entirely logical: You have an Activity that either loads an existing item or creates a new one depending on the action that's passed to it, and then lets the user edit what it has. The UI for 'fill out new item' and 'edit existing item' is the same so it makes sense to roll the two cases into one.

The catch is actually clearly pointed out in the Activity Lifecycle documentation: Android can completely stop and destroy your activity and then create it anew when the user moves back to it.

One place this happens is during configuration changes, like anytime the user tilts his phone to the side and the screen goes from portrait to landscape. The activity gets paused, gets a chance to save state, then stopped. And then it gets created again, with the saved state passed along.

And in the code above, that saved state is completely ignored and a new, empty item is cheerfully inserted into the database, loosing whatever the user had previously entered. Tilt the phone to and fro a few times and you end up with a whole bunch of blank items and a very confused, unhappy user.

The fix is to save the uri of the item that's being working on in onSaveInstanceState() and reload that item in onCreate(). During ACTION_INSERT, insert a new item only when savedInstanceState is null.

In my own code I create a new note only when no saved state is passed along. And I actually go one step further and save the entire original note so I can offer a 'Revert Edits' command.

1 comment:

  1. I know this one is old, but I've been struggling with the same problem based on the same sample code recently and just saw this post now. I figured out the "onSaveInstanceState" approach, too. It appears to work, but not if you push things a little further. If the activity is destroyed and the application is terminated by the system to reclaim memory and other resources, then no instance state is available when the activity is started again and ACTION_INSERT fires a second time.

    As I'm an Android newbie, I don't really know what my other options are. Is the whole "INSERT or EDIT" pattern just broken and should it be done a different way? Should I force my application to restart with a different activity to the one that was open when it was stopped? Did you ever hit the problem I'm having?

    ReplyDelete