fix(android): [SDK-4407] deliver notification events when firebase_messaging is present (#1138)#1152
Open
fadi-george wants to merge 20 commits into
Open
fix(android): [SDK-4407] deliver notification events when firebase_messaging is present (#1138)#1152fadi-george wants to merge 20 commits into
fadi-george wants to merge 20 commits into
Conversation
…stence demo_fm is a copy of the demo app with firebase_core + firebase_messaging added and FCM listeners registered before OneSignal, matching the affected users' setup in issue #1138. Initializing Firebase registers a FirebaseMessagingService in the manifest, which is what triggers the FCM hijack + click-listener routing problems the fix addresses. Also gitignores google-services.json / GoogleService-Info.plist so per-dev Firebase config is not committed. Co-authored-by: Cursor <cursoragent@cursor.com>
…ssaging is present (#1138) Two coexistence bugs caused OneSignal notification click / foreground events to silently vanish on Android when an app also uses firebase_messaging: Bug A — FCM hijack. OneSignal's FCMBroadcastReceiver intent filter requires a <category> matching the app package, a GCM-era convention modern FCM no longer sets. When FlutterFire registers its own FirebaseMessagingService, OneSignal's receiver never matches and pushes are dropped before OneSignal sees them. Fixed by re-declaring the receiver from the plugin manifest with tools:node="replace" and no <category>, so OneSignal's existing receiver matches the modern FCM broadcast. Bug B — channel clobber. OneSignalNotifications is a process-global singleton, but registerWith runs once per Flutter engine. FlutterFire spins up a headless background FlutterEngine and GeneratedPluginRegistrant re-registers us against it, rebinding the shared MethodChannel to the background isolate (which never ran main() and has no listeners). Native onClick/onWillDisplay callbacks were dispatched there and lost. Fixed by: - registerWith no longer rebinds an already-bound channel (ignores the extra background engine), and - onAttachedToActivity authoritatively (re)binds the channel to the engine that hosts the UI, so events always reach the isolate that registered the user's listeners (also covers killed-start where the background engine attaches first). Verified on an emulator with firebase_messaging present: background-tap notifications now fire addClickListener, and foreground pushes reach the willDisplay listener. Repro app: examples/demo_fm. Co-authored-by: Cursor <cursoragent@cursor.com>
…detach Address PR review: - Apply the same idempotent registerWith guard + onAttachedToActivity rebind to OneSignalUser, OneSignalPushSubscription, and OneSignalInAppMessages, so a FlutterFire background engine can't clobber their channels and drop onUserStateChange / onPushSubscriptionChange / IAM lifecycle callbacks. - Gate OneSignalNotifications.onDetachedFromEngine on the detaching engine's messenger so a background engine teardown no longer removes the click listener bound to the live UI engine. - Tighten the #1138 lifecycle comments. Co-authored-by: Cursor <cursoragent@cursor.com>
Collaborator
Author
|
@claude review |
sherwinski
approved these changes
May 28, 2026
Address PR review nit: extract the repeated registerWith guard and onAttachedToActivity rebind logic into bindChannelIfUnbound and rebindChannelToEngine on FlutterMessengerResponder, used by all four singletons. Co-authored-by: Cursor <cursoragent@cursor.com>
Add tools/send_fcm.sh to send non-OneSignal pushes via the FCM HTTP v1 API (notif/data modes) for exercising the FlutterFire path, document it in the README, and gitignore the service-account key. Co-authored-by: Cursor <cursoragent@cursor.com>
Add an iOS setup/testing section (GoogleService-Info.plist, APNs auth key, simulator caveats, log stream workflow for killed-state logs) and document the new send_fcm.sh 'both' mode plus per-platform mode reliability. Co-authored-by: Cursor <cursoragent@cursor.com>
Collaborator
Author
|
@claude review |
bindChannelIfUnbound early-returned on later engines, so a FlutterFire background isolate's messenger never got setMethodCallHandler, breaking Dart->Native calls (MissingPluginException) from an FCM background handler. Always register the incoming handler per engine while keeping the outgoing channel pinned to the first engine (preserves the #1138 Native->Dart fix). Also fix the .gitignore comment to reference examples/demo_fm. Co-authored-by: Cursor <cursoragent@cursor.com>
613d864 to
f2c3f25
Compare
onAttachedToActivity returned early on the new-engine branch without clearing the process-global clickListenerRequested flag, leaving the prior engine's state. A config change on the new engine before Dart re-registered would then re-add the native click listener and drain queued clicks into an isolate with empty _clickListeners, silently dropping them. Reset the flag so it tracks the current engine's Dart state. Co-authored-by: Cursor <cursoragent@cursor.com>
Collaborator
Author
|
@claude review |
…ndow Only buffer clicks before the app's first click listener registers, and cap the buffer size. Prevents unbounded growth when a listener is added then removed without re-registering, since removeClickListener doesn't stop native dispatch and _clickHandlerRegistered never resets. Co-authored-by: Cursor <cursoragent@cursor.com>
Collaborator
Author
|
Would like to wait on OneSignal/OneSignal-Android-SDK#2655 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
One Line Summary
Fix Android notification click & foreground events being silently dropped when
firebase_messaging(FlutterFire) is present in the same app.Motivation
GitHub #1138: since 5.4.0, users running
onesignal_flutteralongsidefirebase_messagingreportOneSignal.Notifications.addClickListenernever firing on Android background/killed notification taps.Root cause:
OneSignalNotificationsowns a process-global staticMethodChannel, butregisterWithruns once per Flutter engine. FlutterFire spins up a headless backgroundFlutterEngineforonBackgroundMessage, andGeneratedPluginRegistrantre-registers OneSignal against it — rebinding the shared channel to the background isolate, which never ranmain()and has no listeners. Native click / willDisplay callbacks were then routed to that dead isolate and dropped. An additional engine-swap case (back out ofMainActivity, then tap a notification) replayed queued clicks into a channel whose Dart end wasn't ready yet.Scope
firebase_messagingcoexistence example (examples/demo_fm/).Changes
OneSignalNotifications.registerWith: bind the shared channel only on the first engine; ignore later engines (e.g. FlutterFire's background engine) so they can't clobber it.OneSignalNotificationsnow tracks whether Dart requested a click listener and toggles the native-SDK subscription across engine/activity lifecycles, so clicks delivered while the channel is detached get queued by the native SDK instead of dispatched into a dead JNI.onAttachedToActivity(messenger): authoritatively rebind the channel to the activity-hosting engine's messenger. On a new engine, defer re-adding the native click listener and let Dart'sregisterClickListenerdrain the queue once the isolate is ready; on the same engine, re-add immediately.OneSignalPlugin: wire theActivityAwarelifecycle (attach/detach + config-change variants) into the hooks above.examples/demo_fm/: new example mirroring the affected setup (OneSignal +firebase_core+firebase_messaging), with a README documenting reproduction and direct-FCM testing.Testing
Manual, on a real device + emulator, OneSignal push tapped in each app state:
Notification clickedfiresNotification clickedfiresNotification clickedfiresForeground
willDisplaylistener also confirmed firing withfirebase_messagingpresent. Verified with runtime instrumentation (since removed) that the channel is bound to the UI isolate's messenger and that queued clicks drain after Dart re-registers.Affected code checklist
Checklist
flutter build apk --release)Made with Cursor