Repository navigation
9.5.0
Fixed
-
Android: Resolved upstream issue invertase/notifee#549 —
cancelTriggerNotifications()andcreateTriggerNotification()JS Promises resolved before the underlying Room database write completed, causing a race where a cancel-then-create pattern could leave inconsistent state. Root cause:WorkDataRepository.insert/deleteAll/deleteById/deleteByIds/updatewere fire-and-forgetvoidmethods that returned immediately while the actual DAO call was still enqueued on a cached thread pool. All five mutation methods now returnListenableFuture<Void>and are chained into the outer future at every call site. Empirical reproduction rate on a Pixel 9 Pro XL before the fix: ~3.3% per attempt, <50ms window. Post-fix: 0 inconsistencies across 150 attempts. -
Android: Fixed a reboot-recovery data-loss bug not mentioned in upstream #549.
NotifeeAlarmManager.rescheduleNotification— invoked fromRebootBroadcastReceiveronBOOT_COMPLETED— fire-and-forgot theWorkDataRepository.update(...)that persists the next-fire timestamp for each recurring alarm. If Android killed the receiver's process before Room drained, the updated anchors were lost and the next reboot rescheduled from stale timestamps, causing recurring notifications to fire at the wrong time or be duplicated. The receiver now collects all per-entity update futures, combines them withFutures.allAsList, waits with an 8-secondFutures.withTimeoutANR safety net, and only then callspendingResult.finish(). -
Android: Fixed an ordering bug in
NotificationManager.doScheduledWorkwherecompleter.set(Result.success())was called before the one-time trigger row was deleted from Room. WorkManager considered the work complete while the row was still pending deletion, leaving a window where a concurrent cancelAll/read could see the zombie row, and where reboot recovery could resurrect a one-shot that had already fired. After the fix, the delete future completes first, then the Worker reports success. -
Android:
NotifeeAlarmManager.displayScheduledNotificationnow awaits the Room update (for recurring alarms) or delete (for one-shots) before callingBroadcastReceiver.PendingResult.finish(). Previously the writes raced against process death inside the receiver'sgoAsync()scope. Uses the same 8-secondFutures.withTimeoutsafety net as the reboot-recovery fix to guarantee the receiver always finishes even if Room is wedged. -
Android:
WorkDataRepository.insertTriggerNotification(...)static helper cleanup — now takes an explicitContextparameter and callsgetInstance(context)instead of the implicitmInstancefield, removing a fragile dependency onNotifeeInitProviderhaving populated the singleton before the first trigger creation.
Changed
-
Android: BEHAVIOR CHANGE — errors from
WorkDataRepositorymutations (e.g.,SQLiteFullExceptionfrom a disk-full device, SQLite corruption, schema migration failures) now propagate as rejections on the JS Promises forcancelTriggerNotifications,cancelTriggerNotification,createTriggerNotification, andcancelAllNotifications(ids). Previously these exceptions were silently swallowed by the fire-and-forget executor, and the JS Promise resolved successfully even when the write had failed. Apps that relied on these methods "always succeeding" may now need to add error handling. This is strictly a correctness improvement — silent write failures were never safe — but it is user-observable, which is why 9.5.0 is a minor version bump. -
Android: LATENCY CHANGE —
cancelTriggerNotifications()andcreateTriggerNotification()Promises now resolve only after the Room write has completed. On a warm database this adds roughly 5–15ms to the perceived latency on a Pixel 9 Pro XL. Apps that schedule many triggers in a tight loop may notice cumulative latency increases. A future release may add a batch API that opens a single Room transaction if this becomes a bottleneck for real apps.
Added
-
Android:
WorkDataRepositoryFutureContractTest— JVM unit test (Mockito +CountDownLatch) verifying all five mutation methods return non-null futures that only complete after the underlying DAO call has returned, and that DAO exceptions propagate throughExecutionException. 13 tests. Runs in the existingtests_junit.ymlCI workflow. -
Android:
WorkDataRepositoryRaceTest— instrumented test underandroidTest/exercising a real Room in-memory database with 100-iteration race scenarios (post-cancel consistency, post-create persistence, delete-by-id visibility, update visibility, concurrent stress). NOT wired into CI yet — tracked in a follow-up issue linked from the PR description. Must be run manually via./gradlew :react-native-notify-kit:connectedDebugAndroidTeston a connected device before merging any change that touches the Room persistence layer. -
Tooling:
scripts/verify-549-fix.sh— automated 5-run harness verification script. Sed-patchesVERIFY_549_AUTO_RUNinapps/smoke/App.tsx, force-stops and relaunches the smoke app five times, parses the per-scenarioRACE549:logcat summaries, aggregates across 100 (Scenario A) and 150 (Scenarios B & C) attempts, and writes with a strict PASS/FAIL verdict. Exit code 0 on PASS, 1 on FAIL — wire into a pre-push hook or future CI emulator job. -
Tooling:
TriggerRaceTestScreen.tsxharness (originally added during the investigation phase and committed here alongside the script changes) now emits one compact JSON line per scenario plus a-DONEterminator, soverify-549-fix.shcan parse each scenario independently without hitting logcat's per-line size limit.
📦 npm: https://www.npmjs.com/package/react-native-notify-kit/v/9.5.0