Skip to content

9.6.0

Choose a tag to compare

@marcocrupi marcocrupi released this 15 Apr 07:28
· 383 commits to main since this release
Immutable release. Only release title and notes can be modified.

Fixed

  • Android: Fixed ObjectAlreadyConsumedException: Map already consumed crash in HeadlessTask.TaskConfig when the same WritableMap instance is reused across headless events or when the taskConfig accessor is read more than once. Root cause: TaskConfig.init mutated the caller's WritableMap directly with putInt("taskId", ...) without copying it first. WritableNativeMap is a consume-once JNI object; once React Native internally called .copy() on it inside HeadlessJsTaskConfig, 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 injected taskId key. (upstream: invertase/notifee#266)

  • Android: RebootBroadcastReceiver, NotificationAlarmReceiver, and AlarmPermissionBroadcastReceiver now wrap their synchronous section in a try/catch/finally guard, ensuring PendingResult.finish() is always called even when ContextHolder init, NotifeeAlarmManager instantiation, or displayScheduledNotification throws 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 in NotifeeAlarmManager.rescheduleNotification and 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). InitProvider now reads Settings.Global.BOOT_COUNT on every app init and, when a boot delta is detected since the last known value persisted in SharedPreferences, triggers a full reschedule of persisted AlarmManager triggers on a background thread. When BOOT_COUNT is 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-wide AtomicBoolean race guard in NotifeeAlarmManager.rescheduleNotifications that prevents double-advancement of past-repeating triggers when both RebootBroadcastReceiver and the cold-start path race after a normal BOOT_COMPLETED delivery. Defense-in-depth note: during Step 6 smoke testing on a Pixel 9 Pro XL we observed Android re-delivering BOOT_COMPLETED to the smoke app the first time the package was launched after a force-stop (no real reboot), which means the reboot receiver and the cold-start path fire concurrently in practice — exactly the scenario the AtomicBoolean is designed for, and the second path logs Reschedule already in progress, skipping duplicate request and exits cleanly. On an OEM device that suppresses BOOT_COMPLETED outright, 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 pure shouldRescheduleAfterBoot decision 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 in handleStaleNonRepeatingTrigger: an Exception from displayNotification is caught and the Room row is still deleted, an Error (e.g. OutOfMemoryError) is propagated as ExecutionException and the row is preserved for a real retry, and a synchronous throw from NotificationManager.displayNotification is intercepted via Futures.submitAsync so the row is still deleted via the resilience branch. RebootRecoveryTest extended 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.sh and scripts/smoke-test-734-scenarios.md — composable manual smoke-test harness for #734. Subcommands cover build, Room DB dump, Settings.Global.BOOT_COUNT read, notifee SharedPreferences dump, filtered logcat capture, device reboot, state wipe, and running the destructive RebootRecoveryTest instrumented suite. Destructive subcommands require an explicit --i-know flag. Five scenarios are documented with expected logcat fingerprints and fail-mode triage.

  • Docs: New "OEM Background Restrictions" section in README.md and packages/react-native/README.md documenting the interaction between vendor autostart policies and scheduled notifications, and how to use the existing getPowerManagerInfo() / openPowerManagerSettings() APIs to guide users through vendor-specific settings screens.

  • Docs: Expanded "Foreground Service Setup (Android 14+)" section in README.md and packages/react-native/README.md with (1) a use-case → foregroundServiceType matrix with Doze exemption, type timeout, and Google Play policy caveats for each type, (2) a callout that mediaPlayback requires 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 the dataSync 6-hour-per-24h cumulative cap, the new mediaProcessing type, the specialUse <property> justification requirement, and the onTimeout(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 or SET_ALARM_CLOCK?" decision guide recommending the SET_ALARM_CLOCK trigger (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.md and packages/react-native/README.md (under "Trigger Notification Reliability") documenting all five AlarmType options — including AlarmType.SET_ALARM_CLOCK, which was already wired end-to-end in the TypeScript enum, validator, and NotifeeAlarmManager Java switch but was previously undiscoverable. SET_ALARM_CLOCK is 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