Stack notifications on Android (plus: get users to see your notifications on JellyBean – phone/phablet UI)

This is something I put together for WhatsHare, a small open source app that I published; I couldn’t find a tutorial that had all of this together in one place.

My app (actually, just one particular Activity) has no UI: it receives an Intent, does its thing and finish()es. The only way I found to notify the user that it’s done something is to add a Notification, as dialogs would get too in the way.

I want the notification to be shown every single time, as it must tell the user “ok, I did something” or “an error occurred”, because she has no other way of knowing if the app even worked!

On the other hand, I don’t want to pollute the notification area with lots of “Success!” notifications, as they’re ultimately pointless once the user has seen them.

The options were to either:

  1. automatically cancel() the notification once the message has been displayed, or
  2. show the message every time, but collapse all notifications into a single one, with an increasing counter

I preferred the latter approach, as I think it feels a little more predictable for the user (also, the other solution requires dedicated services/timers, so it’s probably more complicated).

Here’s the final code , comments follow:

@SuppressWarnings("deprecation")
private void showNotification(int sharedWhat) {
    String title = getString(R.string.app_name);
    // this will be routed to onNewIntent(), SendToGCMActivity is this class
    Intent onNotificationDiscarded = new Intent(this,
            SendToGCMActivity.class);
    PendingIntent notificationIntent = PendingIntent.getActivity(this, 0,
            onNotificationDiscarded, 0);
    Notification notification = null;
    String content = getString(R.string.share_success,
            getString(sharedWhat), outboundDevice.type);
    
    // notificationCounter is a private static AtomicInteger
    int notificationNumber = notificationCounter.incrementAndGet();

    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.drawable.notification_icon, 0)
            .setContentTitle(title)
            .setContentText(content)
            .setTicker(content)
            .setContentIntent(notificationIntent)
            .setDeleteIntent(PendingIntent.getActivity(this, 0, onNotificationDiscarded, 0))
            // update the counter
            .setNumber(notificationNumber);

    if (Build.VERSION.SDK_INT > 15) {
        notification = buildForJellyBean(builder);
    } else {
        notification = builder.getNotification();
    }

    // notifications disappear after the user taps on them
    notification.flags |= Notification.FLAG_AUTO_CANCEL;
    NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    // cancel previous notification to clean up garbage in the status bar
    nm.cancel(notificationNumber - 1);
    // add new notification
    nm.notify(notificationNumber, notification);
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private Notification buildForJellyBean(NotificationCompat.Builder builder) {
    // for some reason Notification.PRIORITY_DEFAULT doesn't show the counter
    builder.setPriority(Notification.PRIORITY_HIGH);
    return builder.build();
}

protected void onNewIntent(final Intent intent) {
    if (intent.hasExtra(Intent.EXTRA_TEXT)) {
        // do your thing
        // ...code goes here
        showNotification(R.string.link);
    } else {
        // user clicked on the notification
        notificationCounter.set(0);
    }
    // this is needed cause even if we set android:theme="@android:style/Theme.NoDisplay"
    // an invisible overlay is laid out on top of the caller activity, we don't want that
    finish();
}

It works like this:

  • you define a static counter for all your notifications, so that the NotificationManager treats them as separate
  • AUTO_CANCEL is set so when the user taps on the notification it disappears
  • setDeleteIntent() is called to have an Intent routed to the Activity’s onNewIntent() method whenever the notification is discarded, so we can reset the counter. If users tap on the notification, onNewIntent() is also called because of notificationIntent being set as content intent
  • since this activity has an intent-filter for action.SEND of type text/plain, we know that an Intent with no EXTRA_TEXT field can only be passed explicitly — that is, by setting the class target when creating the Intent –. We could add some other extra if we wanted to make extra sure that the Intent comes from that specific class
  • whenever a notification needs to be stacked, we cancel() the previous one (using its id, which is always the current counter minus one) and create a new one with the updated count

That’s the magic behind stacked notifications: Android does not show your notifications if you’re just updating them, so you need to cancel the old one and add the new one. If you don’t need to show the message every time, just update the previous notification using its id (that must not change, so no counter at all).

About JellyBean: if you don’t raise the notification’s priority to at least PRIORITY_HIGH it won’t show the counter on phones/phablets for some reason. Also, to make sure that messages are displayed every time on phone/phablets you must set them as tickers; tablets instead show the full message set by setContentText() anyway.

A final note: I’m using NotificationCompat.Builder instead of plain Notification.Builder to support older devices. You can find that class in the support library (android.support.v4.app package). If you’re targeting honeycomb and higher, just use Notification.Builder.

Advertisements

3 thoughts on “Stack notifications on Android (plus: get users to see your notifications on JellyBean – phone/phablet UI)

  1. Hello! I want to do this with my all of my notifications. The catch is: I’m not an app developer or a coder or anything of the sort. If this does what I think it does, stacking notifications, how can I do this on my phone? I have an LG G3 on Verizon running Android 6.0 Marshmallow. I don’t know how to code or even what program I would use to do it. I would love some help. Thanks man!

    Like

    • Hey Ben,
      were you thinking of stacking all notifications for all apps?

      For something like that, I’m afraid you would need to look for an app that does that, and said app will probably work only on rooted phones.

      The notification system is part of the Android OS, so I think that it’s the only way… :-/

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s