A focus app has an awkward property: the better it works, the less time it spends on screen. You start a session and then you are supposed to stop looking at your phone for half an hour. Which means the app is in the background for essentially the entire duration of its core feature.

The background is the one place Android makes you no promises.

The bug we shipped

The first version kept the session in a view model. Remaining time was a value in memory, decremented on a coroutine, rendered as it changed. This is the shape every tutorial teaches, and it is correct for anything the user is looking at.

A focus session is not that. Users would start a twenty-five minute block, lock the phone, put it face down, and come back to a timer at zero, or at the full duration, or gone.

Android kills background processes. This is not a failure; it is the memory model. Your process is a cache of your state, and the system reclaims caches. If your timer lives in memory, your timer is a cache too.

The reports were confusing because the behaviour depended on what else the user was doing. Someone who locked their phone and left it alone usually got a working timer. Someone who locked it, then took a call, then opened a camera, came back to nothing. We spent longer than we should have looking for a race condition before recognising that the pattern was memory pressure.

Storing the end, not the remaining

The fix is to stop counting.

A session persists one number: the instant it will end, expressed on Android's monotonic elapsed-real-time clock, which keeps advancing while the device sleeps and is not affected by the user or the network correcting the wall clock. The remaining time is never stored, because it is not state. It is a rendering of the current time against a fixed endpoint.

So the UI computes end - now whenever it draws. If the process is killed and later recreated, it reads the persisted endpoint and renders the correct remaining time immediately, with no restart, no drift, and no reconstruction. There is nothing to lose because there was never anything accumulating.

This is a small change with a general lesson attached: in-memory counters are state you can lose, and anchors you compute from are not. Any time you find yourself incrementing something on a tick, ask what the anchor is.

The foreground service, and what its notification is for

Persisting the endpoint means the timer is correct whenever the app runs. It does not mean anything happens when the session ends.

For that, a session runs as a foreground service, which is Android's mechanism for work the user has explicitly asked for that must continue while they are elsewhere. It has a cost: it requires a visible ongoing notification, permanently, for the duration.

We initially treated that notification as a compliance requirement and posted something minimal. That was a waste. The notification is the best surface the app has during a session, because it is the only one available without leaving what you are doing.

It now shows the remaining time and the task name, updating as the session runs, with actions for pause and stop. The result is that the most common interaction — "how much longer" — is answered by a glance at the lock screen, which is exactly the interaction you want a focus app to make cheap. Opening the app to check the timer means unlocking the phone, and unlocking the phone during a focus session is how the session ends.

A session that ends also needs a completion alarm registered with the system, so that the moment is not dependent on our process being alive. The service and the alarm are two independent paths to the same instant. Belt and braces, for something whose entire job is to fire at a particular time.

The devices that ignore all of this

There is a category of Android device where a foreground service, a persisted endpoint, and a registered alarm are all still not enough, because the manufacturer's battery management stops the app anyway.

The behaviour has different names on different brands and the effect is the same: your process is stopped, your alarms may be dropped, and there is no API that reports it. The system-level answer is to exempt the app in a manufacturer settings screen that we cannot open programmatically and that moves between OS versions.

We detect the symptom rather than the cause. If a session ends and the completion never fired while the app was not running, we know something removed us, and we say so afterwards with the device-specific instructions we know about. This is unsatisfying, and it is the honest state of the platform. Anyone who tells you they have solved this on every Android device is not being straight with you.

Making a session you will finish

The software keeps the timer honest. Whether the session works is mostly about what you do before you press start.

Name the task, in words, before starting. Not "study" or "work." "Review chapter three notes." "Draft the first section." The timer bounds the time; it cannot bound the scope, and an unbounded scope inside a bounded time is how a session ends with the feeling that nothing happened. Writing it down also stops the first five minutes being spent deciding what counts.

Make the first session shorter than you think you need. Twenty-five minutes is the default everyone knows, and fifteen is a better starting point if your attention is scattered. The purpose of session one is to be finished, not to be impressive. A session you complete makes the next one easier to start; one you abandon does the opposite.

Decide what is allowed to interrupt. Before you begin, not during. A genuine emergency breaks a session. An idea does not — write it on paper and keep going. A message that is not urgent does not. Making this decision in advance means you are not renegotiating it under pressure every time something twitches.

Put the phone where the notification is visible and the phone is not reachable. This is the setup the notification design is for. Face up, across the desk, out of arm's reach. You can see the remaining time; you cannot idly pick it up.

Take the break. The break is what makes the next session possible. Skipping it because the session went well is how the fourth session of the day becomes impossible. Stand up, look at something further than a metre away, get water. Do not open a feed — a break spent scrolling does not return attention, it spends more of it.

Where a timer stops helping

If the task is not clear, a timer measures how long you felt bad rather than how long you worked. That is a real thing that happens and it is not a discipline failure; it is a planning problem wearing a focus problem's clothes. When a session goes badly, the first question is whether the task was defined, not whether you tried hard enough.

If you already know what to do and are simply avoiding it, a timer is genuinely useful, because a fifteen-minute commitment is easier to make than an open-ended one.

And if the work has a natural boundary — reading to the end of a chapter, cleaning until the room is done — a countdown is the wrong shape entirely. Something that measures rather than bounds is what you want there.