A medication reminder scheduled with expo-notifications fires minutes late on some Android 12+ devices; why, and what do you change?
answer
- local schedules sit on the alarm system
- exact only when the app may
- otherwise an inexact fallback
- SCHEDULE_EXACT_ALARM in android.permissions
- boot rescheduling is built in
basics
~20 sOn Android 12+ expo-notifications uses an exact alarm only when the app is allowed to schedule exact alarms; otherwise it falls back to an inexact alarm the system may delay. Declare SCHEDULE_EXACT_ALARM, and handle devices where it is still unavailable.
solid answer
~40 sexpo-notifications schedules local notifications on Android through the system alarm service. Its scheduler uses an **exact, allow-while-idle** alarm when the device runs below Android 12 or the app can schedule exact alarms, and otherwise falls back to an **inexact** allow-while-idle alarm, which the system may batch and deliver late. The library does not declare the exact-alarm permission itself (its manifest adds only `RECEIVE_BOOT_COMPLETED`, used to restore schedules after a reboot), and Expo's documentation says an Android 12+ app needs `SCHEDULE_EXACT_ALARM` for notifications that trigger at an exact time. In an Expo project that goes in `android.permissions` in the app config, followed by a native rebuild. Even then the capability can be unavailable on a device, so for medication the app should notice late delivery, explain it, and never promise to-the-minute timing it cannot guarantee.
code
json · 7 lines{
"expo": {
"android": {
"permissions": ["android.permission.SCHEDULE_EXACT_ALARM"]
}
}
}go deeper
Remember that on Android 12+ exact timing for scheduled notifications needs the SCHEDULE_EXACT_ALARM permission, which expo-notifications does not add for you.
Explain the scheduler's choice between exact and inexact alarms, where the permission is declared in an Expo project and why it needs a native rebuild.
Diagnose late reminders by Android version, manifest contents and channel importance, detect lateness in the app, and communicate honestly when exact timing is unavailable.
Weigh exactness against platform policy and battery rules for a health feature, and decide what timing guarantee the product can actually make to patients.
## How a local reminder is delivered on Android A local notification scheduled with `Notifications.scheduleNotificationAsync` is not kept alive by JavaScript. On Android, expo-notifications stores the request and registers an **alarm** with the system; when the alarm fires, the library builds and posts the notification, then schedules the next occurrence for repeating triggers. The kind of alarm decides how precise the reminder is. expo-notifications' Android scheduler chooses between two: | Condition | Alarm used | Timing | |---|---|---| | Android below 12, or the app can schedule exact alarms | Exact, allowed while idle | At the scheduled time | | Android 12+ without the exact-alarm capability | Inexact, allowed while idle | The system may defer and batch it | So the same code is punctual on one phone and several minutes late on another, which is exactly the report a medication app receives. ## What to change 1. **Declare the permission.** Expo's notifications documentation says that from Android 12 (API 31), scheduling a notification for an exact time requires `<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>`. expo-notifications does not add it; in an Expo project list it under `android.permissions` in the app config, which Expo's permissions guide uses as its own example, and rebuild the native app. An over-the-air update cannot add a permission. 2. **Accept that it can still be unavailable.** Declaring the permission does not guarantee the capability on every device; when it is missing, expo-notifications quietly uses the inexact fallback rather than failing. 3. **Detect lateness.** When a reminder is shown or acted on, compare the actual time with the scheduled one; if deliveries run late on a device, tell the user once and point them to the system setting that allows exact reminders. 4. **Check the channel too.** A user who lowered the medication channel's importance can make an on-time reminder easy to miss; read it with `getNotificationChannelAsync`. ## Setting expectations in the product A medication app should say what it can and cannot promise: - show the scheduled time of each reminder, so the user can notice a late one; - after a late delivery, explain briefly that the phone delivered it late and how to allow exact reminders; - never present a reminder as a safety guarantee, since the user can also silence the channel or the device. ## What already works: reboots expo-notifications' manifest declares `RECEIVE_BOOT_COMPLETED`, and the documentation explains it is used to set up scheduled notifications again when the device restarts. A phone that reboots overnight still delivers the morning dose reminder without the app being opened. ## Why not rely on push instead A server push for each dose adds network dependence and its own delivery delays, and loses the offline guarantee that makes local scheduling attractive for medication. The better fix is to make the local schedule exact where the device allows it and honest where it does not. ## Justify the permission Expo's permissions guide warns that apps using sensitive Android permissions without valid reasons may be rejected at store review. A medication reminder is a clear use case, but the declaration should be a deliberate, documented decision for this app rather than something copied into every project. ## Diagnosing a report - Which Android version and device? Below Android 12 the scheduler always uses the exact path. - Is `SCHEDULE_EXACT_ALARM` in the built manifest, or only in a config that was never rebuilt? - Is the delay consistent (inexact batching) or random and large (the channel or the device's own power management)? - Did the reminders survive a reboot? If not, check that the scheduled requests exist with `getAllScheduledNotificationsAsync()`. ## Verifying the fix After rebuilding with the permission, confirm it end to end: install the new binary on an Android 12+ device, schedule a reminder a few minutes ahead, lock the phone and leave it idle, and compare the delivery time with the schedule. Repeat on a device where the exact-alarm capability is unavailable to see the inexact fallback and make sure the app's lateness message appears. ## Common mistakes - Assuming every scheduled notification is exact on Android. - Adding the permission in app config and shipping it as an OTA update. - Treating a late reminder as a JavaScript timer problem; the schedule does not run in JavaScript.
- Why can't the SCHEDULE_EXACT_ALARM fix ship as an over-the-air update?Permissions are declared in the native Android manifest, which is produced when the native app is built. An over-the-air update replaces the JavaScript bundle and assets only, so the new declaration reaches users only through a rebuilt binary from the store.
- Does a medication app need to reschedule reminders itself after the phone restarts?Not with expo-notifications on Android: the library declares `RECEIVE_BOOT_COMPLETED` in its own manifest and uses it to set up scheduled notifications again when the device boots. The app should still reconcile its plan with `getAllScheduledNotificationsAsync()` on launch to catch any drift.
saying these in an interview costs you the question
- Every local notification on Android fires at exactly the scheduled time
- expo-notifications declares SCHEDULE_EXACT_ALARM automatically
- Scheduled reminders are lost when the phone restarts
- Adding a permission to app.json can ship as an OTA update
- Late reminders come from JavaScript timers being throttled