Android Tip: Effective Intents

Prefer to distinguish intents using data rather than extras.

The forthcoming release of OneBusAway for Android will fix a long-standing bug with launcher shortcuts. Many people like to have shortcuts to stops on their home screen. Let’s say you have shortcuts to Stop A and Stop B, and you do the following steps:

  1. Tap on the shortcut for Stop A to see the arrival info for Stop A.
  2. Press the Home key (not back) to get back to the launcher.
  3. Tap on the shortcut for Stop B.

You would think that you’d see the arrival info for Stop B, right? Wrong. What you’d see is the info for Stop A.

At this point you’ll either say, “oh well, I guess it’s a bug”, or “I don’t care, what’s the point of this story”, or “I’m interested to know why that happens.” If you are the first or second type of person, you may want to stop reading and perhaps go watch cats on Roombas. If you’re the third type, allow me to explain:

Intents are one of the fundamental concepts in Android, and one of the first things you learn when starting Android programming. Intents are, simply put, abstract message objects that describe an operation your application wants to do. How the operation is done, and what the operation does, is very much dependent on the content of the intent. However, the majority of intents will be used to run Activities, the other fundamental concept in Android that mostly describe each screen of UI in your application. So from one Activity, if you want to run another Activity, you’ll often see code like this:

// From within source activity
Intent i = new Intent(this, ShowInfoActivity.class);
startActivity(i);

More often than not, however, you want to pass some data to the activity — for instance, if it wants to access something out of the database, you may want to provide it with the ID of the database row. If you’ve made your way through the Notepad example, you may think the best and easiest way to do this is through an Extras, bits of stuff passed along in your intent that you can define yourself:

// From within source activity
Intent i = new Intent(this, ShowInfoActivity.class);
i.putExtra(ShowInfoActivity.EXTRA_ROWID, rowId);
startActivity(i);

Indeed, this is the easiest and, for the most part, a perfectly acceptable way.

Then at some point in your development you decide: “wouldn’t it be great if users could link directly to their info directly from their home screens.” So you learn how to create shortcuts, you re-read that part of the documentation on intent filters you glossed over the first time, and you whip up the following code:

void returnShortcut(int rowId, String shortcutName) {
    Intent i = new Intent(this, ShowInfoActivity.class);
    i.putExtra(ShowInfoActivity.EXTRA_ROWID, rowId);
    Intent shortcut = new Intent();
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, i);
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, shortcutName);
    setResult(RESULT_OK, shortcut);
    finish();
}

Fantastic! You’ve made your user’s life easier. Unfortunately you’ve also just created the bug I described earlier.

Why? Read that part on intent filters again. You may also want to read the documentation about tasks. But the short explanation is that, for the purposes of intent resolution, extras are ignored when determining whether two intents are equal. So Android will match solely on the Activity name, find the same Activity on the top of your task stack, and just decide to resume that activity rather than creating a new one with the new data.

Fortunately, there’s a solution. The better way is to put the row ID as part of a URI, rather than as a part of an extra. So the previous code becomes something like this:

void returnShortcut(int rowId, String shortcutName) {
    Intent i = new Intent(this, ShowInfoActivity.class);
    i.setData(ContentUris.withAppendedId(BASE_URI, rowId));
    Intent shortcut = new Intent();
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, i);
    shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, shortcutName);
    setResult(RESULT_OK, shortcut);
    finish();
}

BASE_URI can be anything, but it should be something that’s specific to your application. The point is that the Data URI is used in determining whether or not two intents are equal, so the system will end up creating a new Activity for this, even if the same Activity with different data is on your task’s stack.

This is also made easier if you decide to put your data behind a content provider.

As I said, this lesson was learned through personal experience, and only really after the bug was reported by an adept user. The problem is that in order for the bug to be really fixed for users, they will have to recreate all of their shortcuts. Don’t let this happen to your users. When using shortcuts, make sure they aren’t just using extras.

About

I like wearing different hats. Follow me on Twitter. Connect with me on Google+