Engineering
Why Android reminders often don't fire – and how we fix it
Battery savers, Doze mode, vendor customizations. Android makes reliable reminders surprisingly hard. Here's what concretely breaks, and what we do about it.
The single most common support request for medication apps on Android, in some variation, is: „The reminder sometimes fires, sometimes it doesn't." That's rarely a bug in the app or a mistake by the developers – it's a consequence of how Android handles background work. Here's what technically happens, and how RemMedy deals with it.
Three mechanisms Android uses to save power
Since Android 6 („Doze"), Google has gradually introduced mechanisms that suspend apps in the background when the device isn't being used. The three most important:
- Doze mode – when the device sits idle on a table, wake-ups drop sharply. Network access gets batched, WorkManager jobs get deferred, alarms are pushed to „maintenance windows". Crucially, that can take hours.
- App Standby Buckets – every app lands in a bucket depending on usage (active → frequent → rare → restricted). If you only open RemMedy once a day, you'll quickly be in „frequent" or „rare", which stretches background intervals.
- Vendor customizations – and now it gets wild. Xiaomi, Samsung, OnePlus, Huawei, Honor and others have added their own, more aggressive layers on top. Xiaomi's MIUI can fully terminate apps the moment they're backgrounded. Samsung's „optimization" suppresses even explicitly scheduled alarms. The dontkillmyapp.com list has been documenting this for years.
The API primitives that actually work
Android offers two APIs that stay reliable under all these optimizations:
- AlarmManager with
setExactAndAllowWhileIdle()– this variant is allowed to fire even in Doze mode, but the app needs the „SCHEDULE_EXACT_ALARM" permission (since Android 12 granted explicitly via the system setting „Alarms & reminders"). - WorkManager for recurring background work (in our case: refreshing plans, scheduling new alarms, stock checks). It's subject to bucket rules but robust against reboots and app updates.
RemMedy uses both together: every single intake gets an exact alarm. A WorkManager job holds the next 48 hours and re-registers them after reboot or update. All of that happens offline: no push server, no Google services, no internet required.
The three steps the user has to do
Even with a perfectly built alarm stack, some things only the user can unlock. RemMedy asks for them on first launch, in this order:
- Allow notifications – sounds trivial, but since Android 13 it's an explicit runtime prompt. Without this, no one sees a reminder.
- Allow exact alarms – a deep link into the „Alarms & reminders" system setting. Since Android 12 this is app-specific; Google deliberately made it high-friction so only apps that genuinely need it get granted.
- Disable battery optimization – the most important step on many devices. Without this exemption, MIUI (Xiaomi) or OneUI (Samsung) can just kill RemMedy.
We walk through this in the onboarding – using the real system dialogs for notifications and exact alarms. Battery optimization is the edge case: Android forbids apps from requesting the system dialog for it (outside a few narrow exceptions). Our solution: we check the status continuously in the background. If it's still active, the Today tab raises a warning with support links to guides from the most common vendors (Pixel, Samsung, Xiaomi, OnePlus) – because every device hides the switch somewhere else.
We deliberately avoided automatic vendor detection via
Build.MANUFACTURER: too many custom ROMs, too many
sub-brands, and vendor support pages shift over time anyway.
Instead, the user sees a list of the four common targets and
picks one. Less „magical", but more reliable.
Still, it remains a cultural break: users expect an app to „just work", not to have them touch three system settings. We think it's more honest to communicate that openly than to pretend it isn't necessary.
The test reminder as a debug tool
Settings has a „Test reminder" button. It schedules an alarm for 10 seconds in the future. If it doesn't fire, at least one of the three steps above is still open – and the user can figure it out without waiting for the next real reminder. For support, it is a lifesaver: we can instruct to run the test and get a clear signal right away.
What we don't do
A few things that are common in the Android ecosystem and we deliberately avoid:
- No Firebase Cloud Messaging for reminders. FCM would be the „easy" path, but it requires Google services and routes every reminder trigger through Google's servers. Wrong answer for an offline-first app.
- No permanent foreground service. Some apps solve the battery-optimization problem by showing a persistent notification to count as „visibly running". It works – but it's loud, uses energy, and feels like spam to users.
- No escalation push messages. We remind once at the scheduled time, with an optional Premium option to repeat until confirmed. No artificial „don't forget…" pushes after 10 minutes.
Takeaway
Reliable reminders on Android are doable – but a surprising amount of that reliability lies not in the code but in the system settings. We could pretend the app is „plug & play" and take the user criticism when nothing fires. The more honest path is to communicate the setup openly, walk through it, and make it testable. That's exactly what we tried with RemMedy.