Working With Android Notification Channels
Google has introduced notification channels to Android Oreo. Each notification is now associated with a channel, and the user is granted control over how each channel presents notifications.
For developers, channels are a double-edged sword. On the one hand, it is great that users get more fine-grained customization! Channels can prevent annoyed and angry users. On the other hand, you're ceding a lot of control of your notifications over to the user. Tricks that used to work for handling notifications will no longer function with channels.
How to Annoy Your Users Without Really Trying
At Trello, we want to avoid annoying users.
As such, our notification system pre-O was rather conservative. We'd group all notifications into one summary (meaning there was only ever one Trello notification at a time in the tray). In addition, we carefully setup our noises so that, even if a batch of multiple notifications came in simultaneously, we'd still only ping the user once.
In O, all notifications must be associated with a channel. That means even our sole summary notification must go into a channel... but if the user disables that channel, then suddenly everything falls apart. Now, our notifications are no longer grouped and sounds stop playing when they come in. Ouch!
The answer seems to be "create a summary for each channel", but that is a potential recipe for disaster. If you've got a dozen channels, then that means you could have a dozen summary notifications at once! In addition, there's no way to coordinate sounds between channels, meaning that if you fired off one notification in each category, you could be pinging the user a dozen times in a row...
There's another option: don't use summaries at all. If you do that, then the Android framework (on N+) will automatically bundle your notifications together if there are four or more of them. However, if you don't use a summary, there's no way to avoid making a sound for each notification that comes in. That means if you get a batch of, say, five notifications on a channel that plays a sound, the user will hear the same "ping" five times in a row!
My Advice
Solving all these competing problems was a juggling act, but I think I've come up with some good advice that can be applied to any app.
Most of my suggestions assume that you have multiple channels. If your application only requires a single channel, then you are safe from the above conundrums and can safely ignore my advice.
Too Many Cooks
How many channels should you create, anyways?
Initially, my goal was to make as few channels as possible. But in the end, I decided that more choice was better for users. I didn't want anyone to feel like they were throwing the baby out with the bathwater when they disabled a channel.
On the other hand, you don't want so many channels that you overwhelm the user. You want to hit that Goldilocks range - not too many channels, not too few channels, just the right number of them.
The important thing is to have scarcely any noisy channels - that is, channels set with IMPORTANCE_DEFAULT
or above. The more channels that make noise by default, the harder it is to avoid annoying users.
Summary Judgment
Overall, I have two goals for reducing annoyance with notifications:
- Have as few unbundled notifications visible at the same time in the tray.
- Make noises as few times as possible.
These two concerns are in direct conflict with each other: if I don't use any summaries then I get the best bundling, but then I make the most noises. If I use a summary for each channel, I reduce the number of noises, but I maximize the number of notifications visible at once.
My balancing act involved the following ideas:
- Have as few channels as possible make noise by default.
- Use summaries on the channels that make noise by default.
Most of the channels will automatically bundle together, lowering the number of visible notifications. Meanwhile, the channels that do make noise have summaries, so you reduce the noise they make. Ideally, you'd only have one channel that makes any noise whatsoever.
Of course, the user could go into the settings and increase the noisiness of any given channel. But, that's their choice: if the user wants that cacophony then, by George, they will have it!
Shhhhhhh!
I've written previously about how to reduce the number of noisy notifications by only having your summary notification make noise. That all worked fine... when you actually controlled the noise yourself. But on O, you don't get to control the noise your notifications make on a one-off basis.
Thankfully, the support library now has support for the same behavior I wrote about before via NotificationCompat.Builder.setGroupAlertBehavior()
. Even better, this logic has been backported to older devices, so you don’t need my hacky workaround anymore.
By default, it's set to GROUP_ALERT_ALL
, meaning that both the summary and all its children make noise. That's no good. A much better option is GROUP_ALERT_SUMMARY
. If you use that setting, it means that only the summary ever makes noise; none of the children do. Thus, if you show a summary + 3 children, you'd only get noise once. It's the desired behavior in most circumstances.
One note on using the API: you need to call setGroupAlertBehavior()
for both the summary and all children, with the same value for all of them. It's a lot like how you set the same group key for all those notifications. It's not enough to just call it on the summary, or on some of the children.
Measure Twice, Cut Once
Once you create a channel, that's it - you're done. You can later update the name/description but that's it.
As a result, you want to make sure that you get it right the first time. Look through all the setters in NotificationChannel
and make sure you've configured it to your liking. Is the importance level set correctly? Did you assign your custom vibration pattern and sound? Did you check if you want your notification to show up as a launcher icon badge? If you're using notification channel groups, did you assign it to the right group?
It's possible to delete a channel and create a new one, but I would be wary of this tactic. First, users are notified of your shenanigans in the settings, so you can get caught. Second, if a user has customized their channels, they’ll get upset when you keep deleting their settings. And third, it wouldn't surprise me at all if Google has anti-channel-abuse plans in mind, so I wouldn't depend on this working in the long run.
Creating Channels
When should you actually create channels?
I think there are two reasonable times to call NotificationManager.createNotificationChannels()
:
- Startup
- The first time you post a notification
Trello is currently doing it at startup, but we may switch to first notification (just to maximize our startup performance time).
You should make sure to create all your channels at once, rather than adding them piecemeal as you display notifications. That way a user who looks at your app's channel settings gets to see the full collection (rather than you sneaking in new channels dynamically).
Obsolete Settings
Before O, the only way a user could change how a notification is delivered was if you gave them the opportunity to do so within your app. For example, in the Trello app we have settings for which sound to make as well as whether it should vibrate the phone.
In O, those settings are null and void because they're completely controlled by the channels. You no longer have any control over the sound, the vibration, and the lights after you've initially configured the channel.
As such, I recommend removing all your old notification settings on O and above (unless some of them are still outside the realm of what channels can control). You don't want to confuse users by giving them settings that don't do anything.
Also, in lieu of having your own settings, I would consider linking to the Android OS notification settings. That way users who don't know where to find channel settings won't be totally lost.
Locale Changes
The only aspect of channels that you are allowed to change after creation are its name and description. Why? Because the locale might change and you have translations for the new locale!
The only way to do this quickly is to listen to the ACTION_LOCALE_CHANGED
broadcast and recreate all your channels whenever you receive it. It's easy to setup, but also easy to overlook.
Example
Taking all of the above into account, here's what Trello chose to do with Android O Channels:
Name | Description | Importance | Summary? | Badge on launcher? |
---|---|---|---|---|
Attachments | Track the progress of your attachment uploads | MIN | No | No |
Boards | Changes to your boards | LOW | No | Yes |
Cards | Changes to your cards | LOW | No | Yes |
Comments | Comments on your cards | LOW | No | Yes |
Due Soon | Upcoming due dates on your cards | DEFAULT | Yes | Yes |
Memberships | Being added to or removed from cards, boards, and teams | LOW | No | Yes |
Mentions | Comments that mention you directly | HIGH | Yes | Yes |
New Cards | New cards in your lists | LOW | No | Yes |
I'm still not entirely confident we made the right choices - how can you ever be? But the above exhibits the behaviors we desire for now:
- Differentiated channels to allow users to crank up/turn down notifications for stuff they don't care about.
- Most channels make no noise by default, and we let the OS bundle them.
- The channels that do make noise have summaries (to avoid making multiple sounds from a batch of notifications).
- We've limited ourselves to two channels that make noise; thus, in the worst-case scenario, we only beep twice in a row.
- Our only ongoing notification, attachment uploads, does not show a badge on the launcher icon.
I optimized for worst-case scenarios. We have (at most) 3 notifications visible at once (mentions, due soon, and the auto-bundle catch-all group). In addition, two sounds can be played at once (mentions and due soon). It's not perfect, but it will hopefully prevent the user from being driven crazy by our notifications.