Skip to content

[Firebase messaging]: Duplicate notifications on android #17330

@iosephmagno

Description

@iosephmagno

Is there an existing issue for this?

  • I have searched the existing issues.

Which plugins are affected?

Messaging

Which platforms are affected?

Android

Description

_firebaseMessagingForegroundHandler and _firebaseMessagingBackgroundHandler are fired multiple times for the same silent (data-only) notification - with firebase messaging 15.2.1

I'm reproducing the issue on android sdk34 Xiaomim Mi Lite 5g, though I saw past similar issues on IOS and this may still affect IOS as well.

Honestly, I’d rather deal with duplicate notifications than miss them entirely.
As a workaround, I’ve implemented a simple cache of notification IDs and filter out duplicates before displaying any alerts. Example code below:

const String kFirebaseNotificationIds = 'firebase_notification_ids';
...

final content = notification.content as ChatNotificationContent;
debugPrint(“DEBUG notification content ${content.id}");
final notificationIds = SharedPrefs.instance.getStringList(kFirebaseNotificationIds) ?? [];
if (notificationIds.contains(content.id)) {
  debugPrint("DEBUG skip duplicate notification");
  return;
} else {
  notificationIds.add(content.id);
  SharedPrefs.instance.setStringList(kFirebaseNotificationIds, notificationIds);
  ...
  // ShowNotification with flutter local notification 
}

...

// Clear kFirebaseNotificationIds when app is paused

I'm still experiencing issues with Android intermittently throttling or blocking push notifications, only to deliver them all at once later. Disabling battery optimizations and enabling autostart improves reliability somewhat, but overall, notification delivery on Android remains inconsistent.

It would be great if the Firebase team could collaborate with the Android team to address this — ideally establishing a more consistent behavior across devices and limiting the ability of manufacturers to interfere with notification delivery mechanisms.

Reproducing the issue

Send a notification to your device.

Firebase Core version

^3.1.1

Flutter Version

3.24.5

Relevant Log Output

Flutter dependencies

Expand Flutter dependencies snippet
Replace this line with the contents of your `flutter pub deps -- --style=compact`.

Additional context and comments

No response

Activity

iosephmagno

iosephmagno commented on May 5, 2025

@iosephmagno
Author

Hi @russellwheatley, in regards to when notifications randomly stop completely when app is in background, can you please let me know if this GPT answer is valid?

It is suggesting that issue is not really the data-only payload, but the DART isolate being killed by the system when app is quit or in background. And it is suggesting to handle notifications with FirebaseMessagingService (native). I start to think it might be right, on IOS we indeed solved the notification issue by handling them natively with the IOS NotificaitonServiceExtension and using Firebase Messaging plugin to still handle the alert onTap. If GPT is right, we could achieve a similar result by handling notification natively on android as well.

GPT:
Why Native FCM Works More Reliably
When using the Flutter Firebase Messaging plugin, you’re relying on:

  • Dart isolate lifecycle
  • Flutter engine startup
  • Plugin initialization order
  • FirebaseMessaging.onBackgroundMessage() logic

This makes notifications less reliable, especially when:

  • The app is terminated or killed
  • The device is in doze mode / battery optimization
  • Manufacturer-specific restrictions exist (e.g. Xiaomi, Oppo)

In contrast, Native Android (Java/Kotlin) FCM integration:

  1. Runs as a system-level Service
  • Your FirebaseMessagingService subclass is registered with Android itself - not Flutter.
  • It runs even if Flutter hasn’t started or the app is killed.
  1. Receives messages via native OS broadcast
  • Android delivers high-priority data-only messages directly to your service, with no Dart engine boot required.
  1. Does not depend on Flutter plugins being ready
  • Dart and plugin init times (or failures) don’t block it.
  • You can decrypt + show the notification instantly from the native thread.

Why This Is Better for Silent + Custom Alerts
With this setup, you:

  • Bypass Dart entirely for notification logic
  • Have full control over decryption, fallback behavior, retry logic, and visual customization
  • Avoid issues from delayed or dropped Dart isolates

But Isn’t It Still a Data Message?
Yes - it’s still a data-only message, but who’s receiving and handling it changes:

  • FirebaseMessagingService (native): Receiver is Android system (Lifecycle Sensitive?: NO)
  • FirebaseMessaging in Dart: Receiver is Dart (Lifecycle Sensitive?: YES)

TL;DR
You’re replacing a Flutter plugin-based receiver with a native Android system service that is more reliable, faster, and doesn’t require Flutter to be alive - perfect for decrypted, silent notifications.

russellwheatley

russellwheatley commented on May 7, 2025

@russellwheatley
Member

@iosephmagno - I think that chatGPT answer doesn't make sense. We aren't relying on Flutter SDK to receive notifications, and receiving messages isn't reliant on the main Dart isolate running otherwise we wouldn't receive messages. We are using native android API to listen for messages. See receiver: https://github.com/firebase/flutterfire/blob/main/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingReceiver.java

We also need to fire up a background Flutter engine to communicate with your Flutter app otherwise you would never receive the messages in you Flutter background handler.

You are more than welcome to fork off FlutterFire and use it in your own app, you could then post an update with your results if you have found a more reliable alternative. Native updates on android Firebase messaging are few and far between at this point so it wouldn't be that hard to keep it in sync with FlutterFire messaging.

MichaelVerdon

MichaelVerdon commented on May 7, 2025

@MichaelVerdon
Contributor

Hi there, you definitely had the right idea to disable battery optimisations as that tends to cut off background processes as phone vendors such as Xiaomi like to do this to claim more battery life https://dontkillmyapp.com/xiaomi. I have not been able to reproduce this issue using other android devices, in my case a Pixel and an S21. Unfortunately I don't think we can do anything about it.

added
blocked: customer-responseWaiting for customer response, e.g. more information was requested.
and removed
Needs AttentionThis issue needs maintainer attention.
on May 7, 2025
iosephmagno

iosephmagno commented on May 7, 2025

@iosephmagno
Author

@russellwheatley - I discovered something that I think might be interesting for you to know. Writing this would take me 30m but I hope it is helpful.

I was going to implement my native service to handle notifications directly on native, without relying on flutter firebase plugins (we do this already on IOS, as it is the only way to handle encrypted non-silent notifications on IOS with instant delivery).

Anyway, while implementing this solution, I discovered something unexpected that allows our app to consistently display silent-notifications on android.

I added a CustomFirebaseMessagingService.

class CustomFirebaseMessagingService : FirebaseMessagingService() {

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        try {
            // Handle the received FCM message here, if needed.
            // val data = remoteMessage.data     

        } catch (e: Exception) {
            Log.e("CustomFirebaseMessagingService", "Exception occurred", e)
            // Optionally handle the exception or rethrow it
        }

    }

}

As you can see I do nothing in onMessageReceived, I was meant to code there the decryption and show alert, but my discovery makes it unnecessary.

I initialize firebase in MainActivity.kt

     // Initialize Firebase
        try {
            FirebaseApp.initializeApp(this)
        } catch (e: Exception) {
            //Log.i("FCM", "Firebase init failed", e)
        }
        FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
            try {
                if (task.isSuccessful) {
                    //Log.i("FCM", "Token: ${task.result}")
                } else {
                    //Log.i("FCM", "Fetching token failed", task.exception)
                }
            } catch (e: Exception) {
                Log.i("FCM", "Exception in token fetch", e)
            }
        }

Here is the Manifest:

<service
            android:name=".CustomFirebaseMessagingService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false"> <!-- This specifies the service is not exported -->
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

DISCOVERY
For some reason, the notification is passed to Flutter Firebase Messaging even if I disabled the
onBackgroundMessage in main.dart

  //FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); // disabled
  FirebaseMessaging.onMessage.listen(_firebaseMessagingForegroundHandler); 

KEY POINTS:

  • Unlike flutter firebase messaging plugin, CustomFirebaseMessagingService is always fired and FirebaseMessaging.onBackgroundMessage is always fired as consequence.
  • Tested on a few android devices where silent notifications are not displayed if I rely only on flutter firebase messaging plugin and app is in background for some time (minutes, hrs or days).
  • Even when notifications worked with flutter firebase plugins, they tended to appear slower than with this solution.

My best guess is that being my CustomFirebaseMessagingService a FirebaseMessagingService, it somehow causes that the intercepted notification triggers FirebaseMessaging.onBackgroundMessage.

NOTE:
I also coded a foreground service to keep CustomFirebaseMessagingService always live, but I noticed it is not even required. I think android system wakes up it anyway on fcm notification arrival.

POTENTIAL IMPLICATIONS:

  • If this workaround resolves the issue on your end as well, consider including these steps as a recommended Android setup in your documentation.
  • It only requires a few lines of configuration but could prevent this critical issue for many apps.
added
Needs AttentionThis issue needs maintainer attention.
and removed
blocked: customer-responseWaiting for customer response, e.g. more information was requested.
on May 7, 2025
iosephmagno

iosephmagno commented on May 7, 2025

@iosephmagno
Author

as phone vendors such as Xiaomi like to do this to claim more battery life https://dontkillmyapp.com/xiaomi.

Yes, but those limitations aren't the root cause of the Flutter Firebase Messaging issue with notifications. On the same devices, native apps receive notifications and flutter apps don't.
The issue itself is particularly tricky to diagnose due to the many moving parts involved in the cross-platform chain. If we’re lucky to have found something that works, we should thank God and move forward with it.

@russellwheatley this works only on Android. On IOS we had to move to native, as there's no way to make silent-notification quick and reliable on IOS.

Maybe Firebase Messaging might do the same and provide the feature as part of the plugin:

  • create a NotificationExtension (or give to devs a ready skeleton)
  • In didReceive: publish the notification to CFNotificationCenterPostNotification
  • In AppDelegate: trigger Flutter engine when the app is woken by NotificationServiceExtension
  • In main.dart: instruct devs to add AppWakeup which will be fired and receive the notification payload from native.
class AppWakeup {
 static const MethodChannel _channel =
 MethodChannel('com.company.app/wakeup_channel'); // Match Swift channel name from AppDelegate.swift and NotificationService.swift
...

SelaseKay

SelaseKay commented on Jun 9, 2025

@SelaseKay
Contributor

Hi @iosephmagno, I'm unable to reproduce with the firebase_messaging example app. For data-only messages, I observed the handlers were called only once in my case. Could you provide a complete minimal example reproducing this issue?

added
blocked: customer-responseWaiting for customer response, e.g. more information was requested.
and removed
Needs AttentionThis issue needs maintainer attention.
on Jun 9, 2025
google-oss-bot

google-oss-bot commented on Jul 17, 2025

@google-oss-bot

Hey @iosephmagno. We need more information to resolve this issue but there hasn't been an update in 7 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    StaleIssue with no recent activityblocked: customer-responseWaiting for customer response, e.g. more information was requested.platform: androidIssues / PRs which are specifically for Android.plugin: messagingtype: bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @russellwheatley@google-oss-bot@SelaseKay@iosephmagno@MichaelVerdon

        Issue actions

          [Firebase messaging]: Duplicate notifications on android · Issue #17330 · firebase/flutterfire