fix(live-notifications): recover the opt-in set in a cold process - #803
fix(live-notifications): recover the opt-in set in a cold process#803mahmoud-elmorabea wants to merge 1 commit into
Conversation
Android starts a process solely to deliver an FCM push, and no app code runs there: the FCM service wires up the context, so the push module is never registered and its config reads back empty. The enabled-types gate then treated that empty set as "this app never opted in" and dropped the notification, which happened for every live notification delivered after ordinary process death. A dropped `end` was worse than a no-op, stranding the ongoing notification a previous process posted with no way for the user to clear it. Persist the opt-in set at initialization and fall back to it at the gate, so a fresh process can tell "never enabled" from "not loaded yet". A populated config still wins, keeping the opt-in guarantee intact for apps that never enabled the feature and letting a disabled type take effect immediately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sample app builds 📱Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## feature/live-notifications #803 +/- ##
================================================================
+ Coverage 68.40% 68.59% +0.19%
- Complexity 1373 1388 +15
================================================================
Files 229 229
Lines 7542 7550 +8
Branches 1127 1132 +5
================================================================
+ Hits 5159 5179 +20
+ Misses 1985 1976 -9
+ Partials 398 395 -3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
|
Build available to test |
|
📏 SDK Binary Size Comparison Report
|
Shahroz16
left a comment
There was a problem hiding this comment.
Looks good to me. I traced this through the killed-process FCM path, and the saved type set is replaced/cleared correctly, with the live config taking over once it exists. That fixes the practical drop for the bundled templates, including cold end delivery.
One known follow-up, not something I think should hold this PR: a wrapper cold process still does not have the runtime callback or branding config, so custom start/update cannot render and bundled templates use fallback styling. A custom end is improved here because it can now clear the stranded notification. This was already outside this minimal fix, so I think we can handle it separately after release.
Live notifications delivered after ordinary process death were dropped, because the enabled-types gate could not distinguish an app that never opted in from a process that had not loaded the opt-in yet.
Why
Android starts a process solely to deliver an FCM push. No app code runs there —
CustomerIOFirebaseMessagingServiceonly callsSDKComponent.setupAndroidComponent(context)— soModuleMessagingPushFCMis never registered andSDKComponent.pushModuleConfigfalls back toMessagingPushModuleConfig.default(), whose type set is empty. The gate inLiveNotificationHandlerread that as "not enabled" and returned.A dropped
endwas worse than a missed render: the matchingstartwas posted by an earlier live process as an ongoing, non-dismissible notification, so it stayed on screen with no way for the user to clear it.What
LiveNotificationStorepersists the app-wide opt-in set, replacing on each write and clearing when empty, so disabling a type does not leave a stale opt-in behind. The key is deliberately unprefixed to stay clear of the per-activity reclamation sweeps.ModuleMessagingPushFCM.initialize()records the set on every initialization, empty included.A populated config always wins, so the opt-in guarantee still holds for apps that never enabled the feature, and a newly disabled type takes effect immediately rather than waiting for the persisted copy to be rewritten.
Custom types additionally need a render callback, which cannot be persisted; that half is tracked separately. Their
endis already improved here, since the handler cancels a stranded notification when there is nothing to render.Verification
messagingpushunit suite: 279 tests, 0 failures, including 9 new ones covering the cold-process gate both ways,endcancelling a stranded notification, config-wins-over-stale-persisted, replace/clear semantics, and survival of the reclamation sweeps.ktlintclean;apiCheckpasses with no.apichanges (all touched code isinternal).Not device-tested.
Note
Medium Risk
Changes push delivery gating and SharedPreferences opt-in state; behavior is well-covered by new tests but affects notification lifecycle after process death.
Overview
Fixes live notifications being dropped when FCM wakes a cold process after the app was killed. In that case
pushModuleConfig.liveNotificationTypesis empty, so the handler treated opted-in apps the same as apps that never enabled the feature—includingendevents, which could leave a non-dismissible ongoing notification stuck on screen.Persistence:
LiveNotificationStorenow saves the app-wide enabled activity-type set (replace on write, clear when empty).ModuleMessagingPushFCM.initialize()writes that set on every init, including when empty, so disabling types does not leave a stale opt-in.Gating:
LiveNotificationHandlerusesenabledActivityTypes()—live config when non-empty, otherwise the persisted set—so cold FCM delivery can still honor the opt-in while apps that never enabled live notifications stay ignored.Reviewed by Cursor Bugbot for commit c73d8cc. Bugbot is set up for automated code reviews on this repo. Configure here.