Repository navigation
9.6.0
Fixed
-
Android: Fixed
ObjectAlreadyConsumedException: Map already consumedcrash inHeadlessTask.TaskConfigwhen the sameWritableMapinstance is reused across headless events or when thetaskConfigaccessor is read more than once. Root cause:TaskConfig.initmutated the caller'sWritableMapdirectly withputInt("taskId", ...)without copying it first.WritableNativeMapis a consume-once JNI object; once React Native internally called.copy()on it insideHeadlessJsTaskConfig, any further access crashed. Latent in most apps (single events with fresh maps), but observed in production by upstream users with high-frequency headless events. The fix copies the map before mutating, matching upstream@notifee/react-native@9.1.8. Also eliminates a silent side-effect where the caller's map was polluted with the injectedtaskIdkey. (upstream: invertase/notifee#266) -
Android:
RebootBroadcastReceiver,NotificationAlarmReceiver, andAlarmPermissionBroadcastReceivernow wrap their synchronous section in a try/catch/finally guard, ensuringPendingResult.finish()is always called even whenContextHolderinit,NotifeeAlarmManagerinstantiation, ordisplayScheduledNotificationthrows before the async callback is registered. Previously, a synchronous failure would leave the broadcast unterminated and Android would kill the process after ~10s, potentially racing future reboot broadcasts and alarm deliveries. (upstream: invertase/notifee#734) -
Android: Stale non-repeating trigger notifications (timestamp already in the past at reboot-recovery time) are now handled correctly instead of being re-armed as "zombie" alarms that re-fire on every reboot and never clean up from Room. Within a 24-hour grace period, the trigger fires once (matching Android's own past-alarm semantics and user expectation) and the Room row is deleted. Beyond the grace period, the row is deleted silently to avoid showing stale content.
INTERVAL-type triggers are still not rescheduled on reboot — that is a separate, pre-existing bug with its own TODO inNotifeeAlarmManager.rescheduleNotificationand is explicitly out of scope for this fix. (upstream: invertase/notifee#734)
Added
-
Android: Cold-start self-healing for scheduled alarms on OEM devices that suppress
BOOT_COMPLETED(Xiaomi MIUI, Oppo ColorOS, Huawei EMUI, Vivo FuntouchOS).InitProvidernow readsSettings.Global.BOOT_COUNTon every app init and, when a boot delta is detected since the last known value persisted inSharedPreferences, triggers a full reschedule of persistedAlarmManagertriggers on a background thread. WhenBOOT_COUNTis unavailable (custom ROMs, emulators, exotic vendors), falls back to a conservative reschedule on every app start; transient read failures never overwrite a real baseline. Paired with a process-wideAtomicBooleanrace guard inNotifeeAlarmManager.rescheduleNotificationsthat prevents double-advancement of past-repeating triggers when bothRebootBroadcastReceiverand the cold-start path race after a normalBOOT_COMPLETEDdelivery. Defense-in-depth note: during Step 6 smoke testing on a Pixel 9 Pro XL we observed Android re-deliveringBOOT_COMPLETEDto the smoke app the first time the package was launched after aforce-stop(no real reboot), which means the reboot receiver and the cold-start path fire concurrently in practice — exactly the scenario theAtomicBooleanis designed for, and the second path logsReschedule already in progress, skipping duplicate requestand exits cleanly. On an OEM device that suppressesBOOT_COMPLETEDoutright, only the cold-start path runs; either way the zombie re-fire loop is broken. (upstream: invertase/notifee#734) -
Android: Regression tests for DAILY/WEEKLY/HOURLY trigger rescheduling cycle, including DST spring-forward and fall-back edge cases (upstream: invertase/notifee#839, #875).
-
Tests:
InitProviderBootCheckTest— 6 Robolectric unit tests exhaustively covering the pureshouldRescheduleAfterBootdecision helper (first run, same boot, new boot, boot-count decrease, BOOT_COUNT unavailable on subsequent run, BOOT_COUNT unavailable on first run).NotifeeAlarmManagerHandleStaleTest— 3 Robolectric + Mockito unit tests covering the resilient display → delete chain inhandleStaleNonRepeatingTrigger: anExceptionfromdisplayNotificationis caught and the Room row is still deleted, anError(e.g.OutOfMemoryError) is propagated asExecutionExceptionand the row is preserved for a real retry, and a synchronous throw fromNotificationManager.displayNotificationis intercepted viaFutures.submitAsyncso the row is still deleted via the resilience branch.RebootRecoveryTestextended with three new instrumented cases: stale non-repeating row within the 24-hour grace period (fire-once-then-delete), stale non-repeating row beyond the grace period (delete-silent), and a deliberately malformed bundle triggering the resilience branch so the row is still deleted when the late-fire fails. -
Tooling:
scripts/smoke-test-734.shandscripts/smoke-test-734-scenarios.md— composable manual smoke-test harness for #734. Subcommands cover build, Room DB dump,Settings.Global.BOOT_COUNTread, notifeeSharedPreferencesdump, filtered logcat capture, device reboot, state wipe, and running the destructiveRebootRecoveryTestinstrumented suite. Destructive subcommands require an explicit--i-knowflag. Five scenarios are documented with expected logcat fingerprints and fail-mode triage. -
Docs: New "OEM Background Restrictions" section in
README.mdandpackages/react-native/README.mddocumenting the interaction between vendor autostart policies and scheduled notifications, and how to use the existinggetPowerManagerInfo()/openPowerManagerSettings()APIs to guide users through vendor-specific settings screens. -
Docs: Expanded "Foreground Service Setup (Android 14+)" section in
README.mdandpackages/react-native/README.mdwith (1) a use-case →foregroundServiceTypematrix with Doze exemption, type timeout, and Google Play policy caveats for each type, (2) a callout thatmediaPlaybackrequires active audio playback per Play Store policy and must not be declared on silent timers, (3) a new "Android 15+ additional FGS restrictions" subsection covering thedataSync6-hour-per-24h cumulative cap, the newmediaProcessingtype, thespecialUse<property>justification requirement, and theonTimeout(int, int)API 35+ overload, (4) clarification in "OEM Background Restrictions" that Samsung OneUI, Xiaomi MIUI, Huawei EMUI, Oppo ColorOS, and Vivo FuntouchOS aggressive-kill policies affect both scheduled triggers and running foreground services —openPowerManagerSettings()is now documented as the mitigation for both reliability problems, and (5) a new "Timers: foreground service orSET_ALARM_CLOCK?" decision guide recommending theSET_ALARM_CLOCKtrigger (not a custom FGS) for silent rest, cooking, and recovery timer use cases, with an explicit note that the library does not acquire a wake lock on the FGS path so the CPU can still suspend with the screen off on non-Doze-exempt foreground service types. Addresses upstream invertase/notifee#410. -
Docs: New "AlarmType guide" subsection in
README.mdandpackages/react-native/README.md(under "Trigger Notification Reliability") documenting all fiveAlarmTypeoptions — includingAlarmType.SET_ALARM_CLOCK, which was already wired end-to-end in the TypeScript enum, validator, andNotifeeAlarmManagerJava switch but was previously undiscoverable.SET_ALARM_CLOCKis the strongest reliability guarantee Android exposes for a scheduled notification: it renders the alarm-clock icon in the status bar, uses the same primitive as the stock Clock app, and is the least susceptible to OEM aggressive-kill power management policies (Xiaomi MIUI, Oppo ColorOS, Huawei EMUI, Vivo FuntouchOS) — the same class of reliability problem tracked in invertase/notifee#734. Recommended for medication reminders, recovery timers, rest timers, and similar use cases where a missed notification is user-visible damage. (upstream: invertase/notifee#655, merged via #749)
📦 npm: https://www.npmjs.com/package/react-native-notify-kit/v/9.6.0