Rebuild note

Android notification channels do not change their mind

A timer has one job at the end of a session, which is to make a sound. Getting that right on Android meant learning that two separate systems quietly ignore you.

What this is based on Two sound failures in StudyPomo, five selectable chimes, and the channel id that encodes the choice.
In this piece
  1. Notification channels are set once, permanently
  2. The workaround is the channel id
  3. The other system that ignores you: audio focus
  4. What connects the two
  5. What we have not solved

StudyPomo has one moment that matters more than the rest of the app put together. A session ends, and it tells you. Everything else is preparation for that.

There are five chimes to pick from, an optional tick while the timer runs, and ambient sounds that layer over each other. All of that is ordinary product work. Making it reliable on Android meant learning that two separate parts of the platform will accept your instruction and then quietly do something else.

Notification channels are set once, permanently

Since Android 8, a notification’s sound, importance, and whether it vibrates are not properties of the notification. They are properties of the channel the notification is posted to, and channels are created by the app.

The part that catches people is what happens on the second run. A channel’s settings are read once, when the channel is first created on that device. After that they belong to the user, not to you. You can change the sound in code, ship the update, and watch nothing happen, because the channel already exists and your new settings are simply not consulted.

This is deliberate and it is correct. It exists so an app cannot decide to make itself more intrusive after you have already told the system how much of your attention it gets. Knowing why does not help when a user changes the chime in your settings screen and the old one keeps playing.

We hit it exactly that way. The chime picker worked, the preference saved, and the sound did not change.

The workaround is the channel id

There is no method to update a channel’s sound. The only lever is that a different id is a different channel, so if the settings need to change, the id has to change with them.

So StudyPomo’s channel id encodes the choices that are baked into a channel. Pick a different chime, or turn vibration on, and you are posting to a channel that did not exist before, created with the settings you just chose.

It is worth being honest about what this costs, because it is not free.

Every combination a user selects leaves a channel behind, and they are visible. Anyone who opens the app’s notification settings in Android will see more than one entry there. We decided that was better than a chime picker that does not change the chime, but it is a trade, not a clean win.

It also means the id is a schema. Once a version ships, the format cannot be changed casually, because an installed device already has channels named the old way and will keep using them.

The other system that ignores you: audio focus

The second problem was different in cause and identical in feel.

Android arbitrates who is allowed to make noise through audio focus. When a player takes focus, everything else is asked to duck or stop. That is the right behaviour for a music app and completely wrong for what we were building.

We had three separate failures from this, all the same shape. The tick silenced the ambient loop. The completion chime silenced the tick. And, worst of the three, starting a session silenced whatever the user had been listening to.

That last one matters more than it looks. A study timer that stops your music the moment you start focusing has broken the thing you opened it for. Nobody would report that as a bug. They would just stop using it.

The fix is to say plainly that we are not the main event: every player sets its Android audio focus to none. The tick, the ambient loops and the completion alert all now play without asking anyone else to be quiet, which is what a timer should do. Your own music keeps going, and rain over a playlist works because neither one is trying to win.

What connects the two

Both are the platform holding a position about who is in charge, and both punish the same assumption: that setting a value in code means the value is in effect.

A notification channel takes your settings once and then treats them as the user’s. Audio focus takes your request at face value and acts on the whole device. Neither returns an error. Neither shows up in a log. The code reads exactly as though it worked.

The practical lesson is smaller than it sounds. On Android, when something makes a sound, verify it on a device rather than in the code. Change the chime, then listen. Start a session with music playing, then listen. Both of these took minutes to find once we stopped reading and started listening, and neither would ever have surfaced from a unit test.

What we have not solved

Channel proliferation is a real and current problem, not a solved one. A user who tries every chime accumulates a channel for each, and there is no tidy way to remove the unused ones without risking deleting one still in use. We may end up pruning channels that no preference points at any more, and we have not written that yet.

If you know a cleaner approach than encoding settings into the id, we would like to hear it. The address is at the bottom of the page.

Written by Adeboye Oluwatimileyin and Moses-Azuoru George, the two people who are Emberfig. We make money from AdMob inside the apps. This site carries no ads yet. You never pay us and we never sell your data.