Correctly handling bundled Android notifications

Bundled notifications on Android are a tricky business.

Individual, fire-and-forget notifications are simple. You create a Notification, send it to NotificationManagerCompat, and the system handles the rest.

Bundled notifications allow you to group multiple notifications together and present them as a single summary notification. It turns out that bundled notifications are much harder to implement than individual notifications.

I recently refactored Trello's notifications because they never quite did what you expected. There is documentation (1, 2) on bundling notifications but I found the docs lacking when it came to pointing out corner cases and gotchas, so here are my notes.

(I assume some familiarity with the notification system already; if you're just getting started with notifications, then I suggest reading this first.)

Summary Notification

First, it's important to understand that bundled notifications also require a summary notification. The summary is what is displayed instead of each individual notification. (On 7.0+, the system partially takes over the display of the summary, but it's still required to provide one.)

This means you need to send NotificationManagerCompat the following Notifications:

  1. N individual Notifications
  2. 1 summary Notification

There's nothing automatic about this process. You have to configure and send the summary notification yourself. You also have to set a common group key on all of them (so the system knows they're bundled).

Updating The Summary

Because each new notification means updating your summary notification, you'll have to keep track of all your currently showing notifications. This means that you have to persist your notification state somewhere.

Obviously, you're going to need to keep track of new notifications. But this also means you have to keep track of notifications that have been dismissed. Otherwise, the summary might still contain information about notifications no longer contained within the bundle.

What this means is that you're going to have to set Notification.deleteIntent. You can send it to a Service whose sole job is to inform your system that a notification has been dismissed.

Noisy Notifications

Notifications typically alert the user in some way, like making sound and/or vibrating.

Unfortunately, if you're adding multiple notifications at once, each still makes its own individual noise! This creates a cacophony if you create many notifications at once (which can easily happen if the user was offline for a bit).

Our solution was to examine all the notifications we're adding and only enable sounds for one of them. If there's only one notification, then the choice is easy. If there are multiple, then we set sounds on the group summary alone. This keeps the user from being overwhelmed by notification noises.

Cancellation Side Effects

Cancelling a summary notification causes any notifications using that group key to be canceled as well.

What this means is that you cannot cancel a summary notification if there are still any individual notifications in that bundle that you want to display. Normally that's not a problem, but what if you want to go from 2 notifications + summary to a single notification? Ideally, when you only have one notification, it should display itself in all its non-summarized glory. But canceling the summary is the only way to achieve that, at which point the notification disappears!

One attempt I made to fix this problem was to cancel the group then immediately re-add the single notification. However, this looked real bad - you could see the notification disappear/reappear (instead of nicely transitioning) and it would re-notify the user with noise.

The solution I eventually came up with was to disassociate the remaining notification from the group, then cancel the summary. In other words, update the single notification so it's exactly the same as before, but with NO group key. That way the system considers the notification change as only an update (avoiding re-notifying the user), but also does not cancel it when you get rid of the summary.

Pseudocode

Combining all the advice above yielded the following logic when handling any change in the notifications we want to display:

if (0 notifications) {
  cancel all notifications
}
else if (1 notification) {
  display/update the notification
    ...With sounds if new
    ...Specifying a null group key

  cancel group summary
}
else if (2+ notifications) {
  display/update group summary notification
    ...With sounds if there are any new notifications
    ...With a group key
  
  display/update each individual notification
    ...No sounds
    ...With a group key
}

The pseudo-code above is essentially what Trello uses internally and it works well for us. Note: it's important that you have stable notification IDs for everything so that you actually update notifications (instead of creating duplicates).