Wednesday, December 29, 2010

A pitfall in PendingIntent (with solution)

The Android documentation has a nice overview chapter about how to notifiy the user with status bar notifications.

The example text works quite nicely and the user gets informed and can then call back into the application. But when working on Zwitscher it did not work as intended by me. But lets start slowly.

Setting up a notifiction goes along the lines of (taken from the developer guide):


Intent notificationIntent = new Intent(this, MyClass.class);
PendingIntent contentIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, "Title",
"something went wrong", contentIntent);

where a PendingIntent is set up as a "pointer" and stored by the system so that when the user selects the notification in the status bar the target activity specified in the notificationIntent can be called.

Now sometimes you want to attach some additional data to the intent to be delivered - like a longer explanation why your action failed. You would go like:

Intent notificationIntent = new Intent(context,MyClass.class);
notificationIntent.putExtra("key","value");        
notificationIntent.putExtra("key2",someCounter++);

to add the payload. And in MyClass you would get the data via

Intent intent = getIntent();        
Bundle bundle = intent.getExtras();        
String head = bundle.getString("key");        
Integer body = bundle.getInt("key2");

Now when the notification fires,  the intent is created and attached to the PendingIntent and this shows up in the status bar

User then selects the status bar to see the longer message and presses this area to see the full details. This means that the system delivers "out of the blue" the created Intent message and thus starts MyClass-activity, which then pulls the payload from the intent.

When you do this a few times in a row you will see that the passed counter (someCounter) does increase in your sending activity, but that the receiver always shows the initial value. Canceling the notification in the sender does not help here.

This comes from the fact, that the system does not assume that only because we pass a new Intent object to the pending intent, we want this new intent object to be delivered and just keeps the old (initial) pending intent alive.

To get the desired semantics, we need to pass a flag to tell the system:

PendingIntent pintent = 
PendingIntent.getActivity(context,0,intent,PendingIntent.FLAG_CANCEL_CURRENT);


This flag (FLAG_CANCEL_CURRENT) tells the system that the old pending intent is no longer valid and it should cancel (=remove) it and then create a fresh one for us. There are more possible flags, which are described on the javadoc page for PendingIntent.

 


You can see a full example in the Zwitscher source code on github in the

And remember that Zwitscher is live on the Android market - download and try it :-)

9 comments:

Ondra said...

Have you tried to compile RHQ agent to android, just for fun? Ondra Zizka

longchuvn said...

Thank for your sharing, save my time for this issue.

longchuvn said...

Thanks

ayşegül said...

I wonder something, when we use this flag does the previous notification gets canceled from status bar? I will try your solution anyway but I am just curious now.

Antonio J. said...

Thank you very much for post your knowledge about Intents, you save me hours of debugging ;)

Anuj Devasthali said...

Thanks a lot... :-D

Reena Nachare said...

When i was using PendingIntent.FLAG_CANCEL_CURRENT, then only one notification can able to show the data others get cancelled autoamtically, why this happens may know?

Shrawan said...

thank you for sharing !

Ryan Brooks said...

It's 5 years now after you posted this but thank you so much. Saved my life on a huge bug I've been having,