Translate

Mittwoch, 17. Dezember 2014

Missing extra content in PendingIntents


What the...?


In this post, I am going to describe a common misconception about
PendingIntents in Android and how they can fool you, if you don't
know exactly what you are doing. An annoying experience almost
every developer makes in the beginning, is to use PendingIntents for various actions
in the future and getting puzzled if they recognize that it doesn't matter what they are
putting into the Intent, they always get the same PendingIntent with the same
extra content. You want to know how to put different information's into your
PendingIntent and why it seems that the extra content does not change?
Read 
my post below!

tl;dr? You can also directly look at the solution at the end!

PendingIntents


Usually you will use PendingIntents as a kind of token, that you can provide to third party
applications to execute parts of your app. Some examples would be to start an Activity
after a Notifcation was clicked or canceled by the user. Or you want the AlarmManager
to start your application in some point in the future.

However, almost every android developer stumbled already upon Intents and used them to
start new Activities, send small messages to a Service or provide information's within the
Bundle inside of the Intent, also called extra content.

The problem


Like in a normal Intent you can add some extra content to the Intent that is
given to the PendingIntent to launch the Activity. Usually you will do this to
distinguish between different startup modes or just to determine the parent
of the PendingIntent.

Let's assume you develope a small chat and you want to display a Notification
to the user, every time your application is in background and your service
recognizes that a new member entered the chatroom.
When the notification get clicked, it should start your ChatActivity and hand over
the name of the recently logged in member.

You will end up to do something simmilar to this:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
 * Created by Steve on 18.12.2014.
 */
public final class ChatNotificationCreator {

    public static final String MEMBER_NAME = "org.awesomechat.ChatNotificationCreator.name";
    public static final int NOTIFICATION_ID = 1338;
    
    private ChatNotificationCreator() {}

    public static void showNewMemberNotification(String memberName, Context context) {
        Intent chatIntent = new Intent();
        chatIntent.setClass(context, ChatActivity.class);
        chatIntent.putExtra(MEMBER_NAME, memberName);
        PendingIntent resultPendingIntent = PendingIntent.getActivity(context, 0, chatIntent, 0);

        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(context)
                        .setSmallIcon(R.drawable.notification_icon)
                        .setContentTitle(context.getString(R.string.notification_title))
                        .setContentText(context.getString(R.string.notification_message))
                        .setAutoCancel(true)
                        .setLights(Color.GREEN, 100, 100) //green works for most phones
                        .setPriority(NotificationCompat.PRIORITY_MAX)
                        .setContentIntent(resultPendingIntent);

        NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
    }
}

The important part is the line 15, where the PendingIntent gets created.

Once your done, you will be happy that this code will actually opens your ChatActivity.
If you tap on the Notification and if you created an applicable ChatActivity.class as well,
you can determine the name of the new member by simply extract it out of the Intent via:


1
intent.getExtras().getString(ChatNotificationCreator.MEMBER_NAME, "");
(be aware of possible NPE here if  there are no extras given to the Intent, e.g. your Activity is exported)

But now the magic happens!

A second member enters the room while your chat is in background.
The notifcation will be created and displayed. You tap on it, the ChatActivity will open,
but boom - the name seems to be still the same as for the first member.

And the third member will also trigger a Notification and it will open the Activity again,
but nevertheless, the name displayed is still the one from the first logged in member.

So why this is happening?


A PendingIntent as said, is used to start an Activity or Service in the future, from 
an application or process that is not yours. It's intented that there is only one 
PendingIntent at the time for the same kind of action. Therefore, even if you change
the information's given by the Intent that will be launched, it is still the same PendingIntent.
The system will ignore your new created PendingIntent.

Source: Quote from Google Reference:
[...]Because of this behavior, it is important to know when two Intents are considered to be the same for purposes of retrieving a PendingIntent. A common mistake people make is to create multiple PendingIntent objects with Intents that only vary in their "extra" contents, expecting to get a different PendingIntent each time.[...]
In my humble opinion, this behaviour is kind of logical, but not clear enough for developers.
Although the api says for the return value of the method PendingIntent.getActivity()

Returns an existing or new PendingIntent matching the given parameters.

I doubt that the majority of developers will directly guess that their received Intent will be
the same, even if they change the information's containing in the extra content.

The Solution


The solution for this problem is quite simple. If we want to update the current PendingIntent,
because we just want to change the containing information's, but not the Activity/Service that
will be launched, then we need to add PendingIntent.FLAG_UPDATE_CURRENT as a parameter for getActivity, likes this:

1
PendingIntent resultPendingIntent = PendingIntent.getActivity(context, 0, chatIntent, PendingIntent.FLAG_UPDATE_CURRENT);

If we want to cancel the recently created PendingIntent, because we (for example) want
to change the Activity to launch, than we add  PendingIntent.FLAG_CANCEL_CURRENT 


1
PendingIntent resultPendingIntent = PendingIntent.getActivity(context, 0, chatIntent, PendingIntent.FLAG_CANCEL_CURRENT);

This also applies for services, with getService(...).

I hope you like this post and I was able to help someone.
Leave your comments below!

Cheers!



SourcesGoogle Reference PendingIntent

Keine Kommentare:

Kommentar veröffentlichen