Is there an existing issue for this?
Which plugins are affected?
Messaging
Which platforms are affected?
Android
Description
I'm trying to understand whether the following behavior is intentional or a bug, since it appears to differ from what the official documentation describes. I'd appreciate any clarification.
Observed behavior (Android, terminated launch)
When the app is opened from a terminated state by tapping a notification, both of the following fire for the same message:
FirebaseMessaging.onMessageOpenedApp stream — emits the message
FirebaseMessaging.instance.getInitialMessage() — returns the same message
If an app follows the pattern shown in the official docs (registering an onMessageOpenedApp listener AND calling getInitialMessage()), the handler ends up being invoked twice for a single notification tap.
What the docs say
The Handling Interaction page assigns each app state to a single API:
- Terminated →
getInitialMessage()
- Background →
onMessageOpenedApp
- Foreground →
onMessage
Reading this, I expected a terminated launch to surface the message through getInitialMessage() only, and onMessageOpenedApp to stay silent in that case.
What I noticed in the Android plugin source
Looking at FlutterFirebaseMessagingPlugin.java in v16.4.3, I noticed:
onAttachedToActivity (lines 101–111) manually invokes onNewIntent(mainActivity.getIntent()) at attach time if the launching intent has extras.
onNewIntent (lines 545–590) does two things: it stores the message so getInitialMessage() can return it later (initialMessage = remoteMessage;) and it invokes Messaging#onMessageOpenedApp on the method channel (channel.invokeMethod("Messaging#onMessageOpenedApp", message);).
That would explain why both APIs surface the same message on a terminated launch. I wanted to check whether this dual-surfacing is intentional (in which case maybe the docs could mention it), or whether the channel.invokeMethod("Messaging#onMessageOpenedApp", ...) in this specific attach-time path is unintended.
Not caused by double-registered listeners
I added logging around every .listen() call and confirmed that FirebaseMessaging.onMessageOpenedApp.listen(...) is registered exactly once per app launch. The duplication does not come from multiple subscriptions on the app side.
Local experiment (for reference)
I tried a local patch on the pub-cache copy of FlutterFirebaseMessagingPlugin.java that comments out the manual onNewIntent(mainActivity.getIntent()) call in onAttachedToActivity (line 108). With this patch:
getInitialMessage() still returns the correct message via the Intent-based fallback path (lines 272–340).
onMessageOpenedApp stream no longer fires on terminated launch.
- Verified 3/3 times: exactly one handler invocation per notification tap, via
getInitialMessage().
This makes me wonder whether the channel.invokeMethod("Messaging#onMessageOpenedApp", ...) on terminated launch might be unnecessary, since the Intent-based fallback inside getInitialMessage() already covers that case. Is that understanding correct?
Reproducing the issue
- Follow the official Handling Interaction sample:
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
final initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) _handleMessage(initialMessage);
- Send a notification to an Android device.
- Force-stop the app (terminated state).
- Tap the notification to launch the app.
_handleMessage is called twice for the single tap (once via the stream, once via getInitialMessage()).
Reproduced consistently (6 out of 6 attempts across firebase_messaging 16.2.2 and 16.4.3) on a physical device.
Firebase Core version
4.12.1
Flutter Version
3.41.9
Relevant Log Output
[SETUP_START] setupRemoteNotification called count=1
[LISTEN_BEFORE] before onMessage/onMessageOpenedApp listen count=1
[LISTEN_AFTER] after onMessage/onMessageOpenedApp listen count=1
[C_ROUTE] onMessageOpenedApp handler entered (via onMessageOpenedApp stream) push=<id>
stackHead: onMessageOpenedApp handler | _rootRunUnary | _CustomZone.runUnary
[SETUP_AFTER_GET_INITIAL] getInitialMessage returned push=<id> isNull=false
[D_ROUTE] calling onMessageOpenedApp handler via getInitialMessage push=<id>
[D_ROUTE] onMessageOpenedApp handler entered (via getInitialMessage) push=<id>
stackHead: onMessageOpenedApp handler | setupRemoteNotification (via getInitialMessage)
([C_ROUTE] and [D_ROUTE] are custom log markers I added to distinguish the two entry paths. The stack traces confirm that the two calls originate from different call sites: one from the stream listener, one from the explicit getInitialMessage() result. Same push id in both.)
Flutter dependencies
Not included by user preference.
Additional context and comments
Question
Is the current Android behavior (both onMessageOpenedApp stream and getInitialMessage() surfacing the same message on terminated launch) intended?
- If yes, would it be possible to mention this in the docs so users can add message-id deduplication?
- If not, would the fix be to skip the
channel.invokeMethod("Messaging#onMessageOpenedApp", ...) in the attach-time onNewIntent path (i.e. equivalent to removing the manual onNewIntent(getIntent()) call in onAttachedToActivity)?
Thanks for your time.
Environment
- firebase_messaging: 16.4.3
- Device: Pixel 5a
- Android version: 14
Is there an existing issue for this?
Which plugins are affected?
Messaging
Which platforms are affected?
Android
Description
I'm trying to understand whether the following behavior is intentional or a bug, since it appears to differ from what the official documentation describes. I'd appreciate any clarification.
Observed behavior (Android, terminated launch)
When the app is opened from a terminated state by tapping a notification, both of the following fire for the same message:
FirebaseMessaging.onMessageOpenedAppstream — emits the messageFirebaseMessaging.instance.getInitialMessage()— returns the same messageIf an app follows the pattern shown in the official docs (registering an
onMessageOpenedApplistener AND callinggetInitialMessage()), the handler ends up being invoked twice for a single notification tap.What the docs say
The Handling Interaction page assigns each app state to a single API:
getInitialMessage()onMessageOpenedApponMessageReading this, I expected a terminated launch to surface the message through
getInitialMessage()only, andonMessageOpenedAppto stay silent in that case.What I noticed in the Android plugin source
Looking at
FlutterFirebaseMessagingPlugin.javain v16.4.3, I noticed:onAttachedToActivity(lines 101–111) manually invokesonNewIntent(mainActivity.getIntent())at attach time if the launching intent has extras.onNewIntent(lines 545–590) does two things: it stores the message sogetInitialMessage()can return it later (initialMessage = remoteMessage;) and it invokesMessaging#onMessageOpenedAppon the method channel (channel.invokeMethod("Messaging#onMessageOpenedApp", message);).That would explain why both APIs surface the same message on a terminated launch. I wanted to check whether this dual-surfacing is intentional (in which case maybe the docs could mention it), or whether the
channel.invokeMethod("Messaging#onMessageOpenedApp", ...)in this specific attach-time path is unintended.Not caused by double-registered listeners
I added logging around every
.listen()call and confirmed thatFirebaseMessaging.onMessageOpenedApp.listen(...)is registered exactly once per app launch. The duplication does not come from multiple subscriptions on the app side.Local experiment (for reference)
I tried a local patch on the pub-cache copy of
FlutterFirebaseMessagingPlugin.javathat comments out the manualonNewIntent(mainActivity.getIntent())call inonAttachedToActivity(line 108). With this patch:getInitialMessage()still returns the correct message via the Intent-based fallback path (lines 272–340).onMessageOpenedAppstream no longer fires on terminated launch.getInitialMessage().This makes me wonder whether the
channel.invokeMethod("Messaging#onMessageOpenedApp", ...)on terminated launch might be unnecessary, since the Intent-based fallback insidegetInitialMessage()already covers that case. Is that understanding correct?Reproducing the issue
_handleMessageis called twice for the single tap (once via the stream, once viagetInitialMessage()).Reproduced consistently (6 out of 6 attempts across firebase_messaging 16.2.2 and 16.4.3) on a physical device.
Firebase Core version
4.12.1
Flutter Version
3.41.9
Relevant Log Output
(
[C_ROUTE]and[D_ROUTE]are custom log markers I added to distinguish the two entry paths. The stack traces confirm that the two calls originate from different call sites: one from the stream listener, one from the explicitgetInitialMessage()result. Samepushid in both.)Flutter dependencies
Not included by user preference.
Additional context and comments
Question
Is the current Android behavior (both
onMessageOpenedAppstream andgetInitialMessage()surfacing the same message on terminated launch) intended?channel.invokeMethod("Messaging#onMessageOpenedApp", ...)in the attach-timeonNewIntentpath (i.e. equivalent to removing the manualonNewIntent(getIntent())call inonAttachedToActivity)?Thanks for your time.
Environment