firebase / flutterfire

πŸ”₯ A collection of Firebase plugins for Flutter apps.
https://firebase.google.com/docs/flutter/setup
BSD 3-Clause "New" or "Revised" License
8.67k stars 3.97k forks source link

Firebase Cloud Messaging] getInitialMessage() returns non null notification even after it is consumed. #10768

Open suhail600 opened 1 year ago

suhail600 commented 1 year ago
Future<void> onSelectBackgroundNotification() async {
    RemoteMessage? message =
        await FirebaseMessaging.instance.getInitialMessage();

    if (message?.notification != null) {
      _handleMessage(message!.data);
      message = null;
      log('background Message has been opened');
    }

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      RemoteNotification? notification = message.notification;
      if (notification != null) {
        _handleMessage(message.dat,);
      }
    });
  }
  1. the code runs when app is in background state and handle message navigates to a page.

    this code snippet is worked --

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      RemoteNotification? notification = message.notification;
      if (notification != null) {
        _handleMessage(message.dat,);
      }
    });
  2. when we hot restart from there --

    RemoteMessage? message =
        await FirebaseMessaging.instance.getInitialMessage();
    this return old data  of consumed message (fromstep 1) .
    and is expected to work atlest 2 to 3 times , then discarded.

    Please fix this , or give any suggestion

darshankawar commented 1 year ago

@suhail600 Can you provide flutter doctor -v, plugin version, platform on which you are seeing this behavior along with complete minimal reproducible code sample ?

Also check if you see the same behavior using plugin example too or not.

suhail600 commented 1 year ago

@suhail600 Can you provide flutter doctor -v, plugin version, platform on which you are seeing this behavior along with complete minimal reproducible code sample ?

Also check if you see the same behavior using plugin example too or not.

this is the flutter doctor -v

[√] Flutter (Channel stable, 3.7.6, on Microsoft Windows [Version 10.0.22621.1413], locale en-IN) β€’ Flutter version 3.7.6 on channel stable at C:\Users\suhail\development\sdks\flutter β€’ Upstream repository https://github.com/flutter/flutter.git β€’ Framework revision 12cb4eb7a0 (6 weeks ago), 2023-03-01 10:29:26 -0800 β€’ Engine revision ada363ee93 β€’ Dart version 2.19.3 β€’ DevTools version 2.20.1

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0) β€’ Android SDK at C:\Users\suhail\AppData\Local\Android\sdk β€’ Platform android-33, build-tools 31.0.0 β€’ Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java β€’ Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301) β€’ All Android licenses accepted.

[√] Chrome - develop for the web β€’ Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[X] Visual Studio - develop for Windows X Visual Studio not installed; this is necessary for Windows development. Download at https://visualstudio.microsoft.com/downloads/. Please install the "Desktop development with C++" workload, including all of its default components

[√] Android Studio (version 2022.1) β€’ Android Studio at C:\Program Files\Android\Android Studio β€’ Flutter plugin can be installed from: https://plugins.jetbrains.com/plugin/9212-flutter β€’ Dart plugin can be installed from: https://plugins.jetbrains.com/plugin/6351-dart β€’ Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)

[√] VS Code (version 1.77.1) β€’ VS Code at C:\Users\suhail\AppData\Local\Programs\Microsoft VS Code β€’ Flutter extension version 3.62.0

[√] Connected device (4 available) β€’ sdk gphone x86 (mobile) β€’ emulator-5554 β€’ android-x86 β€’ Android 11 (API 30) (emulator) β€’ Windows (desktop) β€’ windows β€’ windows-x64 β€’ Microsoft Windows [Version 10.0.22621.1413] β€’ Chrome (web) β€’ chrome β€’ web-javascript β€’ Google Chrome 111.0.5563.148 β€’ Edge (web) β€’ edge β€’ web-javascript β€’ Microsoft Edge 112.0.1722.39

[√] HTTP Host Availability β€’ All required HTTP hosts are available

! Doctor found issues in 1 category.

suhail600 commented 1 year ago

@suhail600 Can you provide flutter doctor -v, plugin version, platform on which you are seeing this behavior along with complete minimal reproducible code sample ?

Also check if you see the same behavior using plugin example too or not.

heres the minimal complate code,

import 'dart:async';
import 'dart:convert';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

const AndroidNotificationChannel channel = AndroidNotificationChannel(
  'high_importance_channel',
  'High Importance Notifications', 
  description: 'This channel is used for important notifications.',
  importance: Importance.high,
  playSound: true,
);

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
}

class FirebasePushNotification {
  //initialization
  initialize() async {
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);

    await FirebaseMessaging.instance
        .setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );

    getIosPermission();

    onSelectBackgroundNotification();

    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      RemoteNotification? notification = message.notification;
      Map<String, dynamic> data = message.data;
      if (notification != null) {
        flutterLocalNotificationsPlugin.show(
          notification.hashCode,
          notification.title,
          notification.body,
          NotificationDetails(
            android: AndroidNotificationDetails(
              channel.id,
              channel.name,
              channelDescription: channel.description,
              playSound: true,
              color: Colors.blue,
              icon: '@mipmap/ic_launcher',
            ),
            iOS: const DarwinNotificationDetails(),
          ),
          payload: jsonEncode(data),
        );
      }
    });

    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');

    const DarwinInitializationSettings initializationSettingsDarwin =
        DarwinInitializationSettings(
      requestSoundPermission: false,
      requestBadgePermission: false,
      requestAlertPermission: false,

    );

    InitializationSettings initializationSettings =
        const InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsDarwin,
    );

    flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse: onSelectForegroundNotification,
    );
  }

  Future<void> getIosPermission() async {
    NotificationSettings settings =
        await FirebaseMessaging.instance.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );

  }

  Future<void> onSelectBackgroundNotification() async {

    RemoteMessage? message =
        await FirebaseMessaging.instance.getInitialMessage();

      if (message?.notification != null) {
        _handleMessage(message!.data, true);
      }
    }

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      RemoteNotification? notification = message.notification;
      if (notification != null) {
        _handleMessage(message.data, true);
      }
    });
  }

  Future<dynamic> onSelectForegroundNotification(
      NotificationResponse notificationResponse) async {
    var data = jsonDecode(notificationResponse.payload ?? '');
    print('data $data');
    _handleMessage(data, false);
  }

  void _handleMessage(Map<String, dynamic> data) {
   print('count');
  }

and is called from bottom nav page's initstate like

final firebase = FirebasePushNotification();

 @override
  void initState() {
    firebase.initialize();
    super.initState();
  }
suhail600 commented 1 year ago

@suhail600 Can you provide flutter doctor -v, plugin version, platform on which you are seeing this behavior along with complete minimal reproducible code sample ?

Also check if you see the same behavior using plugin example too or not.

here are the plugin versions,

firebase_core: ^2.4.1
firebase_messaging: ^14.2.1

and the example in the repo cant be used to replicate , as the use case and implementation differs .(even though the methods are same). It was also experienced in my previous projects as well , and already this issue is raised by someone before me and closed it without the solution as no activity. you can replicate the issue with the details provided.

select a notification while placing the app in backgound , then app opens to a specifc screen by the handler function. then try to restart the app then the FirebaseMessaging.instance.getInitialMessage()

gives non null message with the previous message data that consumed while app was in the background, and the message handler functions and goes to that page.

darshankawar commented 1 year ago

@suhail600 Thanks for the update. Can you upgrade plugins to latest version and try again ? Ideally getInitialMessage() should be called only once and once consumed, it should be null, which leads me to this case : https://github.com/firebase/flutterfire/issues/4188

suhail600 commented 1 year ago

@suhail600 Thanks for the update. Can you upgrade plugins to latest version and try again ? Ideally getInitialMessage() should be called only once and once consumed, it should be null, which leads me to this case : #4188

tried updating the plugins, firebase_core: ^2.9.0 firebase_messaging: ^14.4.0 these are the latest ones and same issue persists.

can u once again go trough the detailed code and issues provided? to find any issue , if not it is with the plugin itself and please fix it as its a major issue.

darshankawar commented 1 year ago

Thanks for the update. Using the code sample provided and details mentioned, running on Android, seeing the same behavior as reported. ie, after notification is tapped, the remoteMessage is consumed, Then restart the app to see the reported behavior.

Keeping this issue open for team's attention and thoughts on current and expected behavior.

/cc @Lyokone

suhail600 commented 1 year ago

Thanks for the update. Using the code sample provided and details mentioned, running on Android, seeing the same behavior as reported. ie, after notification is tapped, the remoteMessage is consumed, Then restart the app to see the reported behavior.

Keeping this issue open for team's attention and thoughts on current and expected behavior.

/cc @Lyokone

Thanks for code verification , please alert when the issue is fixed so can check and close the issue.

rainbowMeh commented 1 year ago

For me, just reopen the app then getInitialMessage() get called and return non null data. And it only happens twice tho.

What weird is The payload of the first returned data and the second time return data is different

Here is the first one

{
    "senderId": null,
    "category": null,
    "collapseKey": "collapseKey",
    "contentAvailable": false,
    "data": {
        "body": "Test Notification Only, Please Ignore",
        "type": "type",
        "title": "Notifcation"
    },
    "from": "from",
    "messageId": "messageId",
    "messageType": null,
    "mutableContent": false,
    "notification": {
        "title": "Notifcation Android",
        "titleLocArgs": [],
        "titleLocKey": null,
        "body": "Test Notification Only, Please Ignore !!",
        "bodyLocArgs": [],
        "bodyLocKey": null,
        "android": {
            "channelId": "channelId",
            "clickAction": null,
            "color": null,
            "count": null,
            "imageUrl": null,
            "link": null,
            "priority": 0,
            "smallIcon": null,
            "sound": null,
            "ticker": null,
            "tag": null,
            "visibility": 0
        },
        "apple": null,
        "web": null
    },
    "sentTime": 1682496667363,
    "threadId": null,
    "ttl": 2419200
}

and here for the second one

{
    "senderId": null,
    "category": null,
    "collapseKey": "collapseKey",
    "contentAvailable": false,
    "data": {
        "body": "Test Notification Only, Please Ignore",
        "type": "type",
        "title": "Notifcation"
    },
    "from": null,
    "messageId": "messageId",
    "messageType": null,
    "mutableContent": false,
    "notification": null,
    "sentTime": 0,
    "threadId": null,
    "ttl": 2419200
}
wahabsohail commented 1 year ago

I am encountering the same issue where the getInitialMessage() function does not return null consistently. When the notification is consumed, it returns the same data three times, and only on the fourth restart of the application its finally return null.

here are the plugin versions,

firebase_core: ^2.13.0
firebase_messaging: ^14.6.1

My complete code,

class NotificationRegister {
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  late Map messageData;

  Future<void> registerNotification() async {
    await messaging.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );
    await messaging.setForegroundNotificationPresentationOptions(
      alert: true, // Required to display a heads up notification
      badge: true,
      sound: true,
    );

    // Get initial notification data
    await FirebaseMessaging.instance.getInitialMessage().then(
      (RemoteMessage? message) async {
        if (message != null) {
          onClickRoute(message, "getInitialMessage");
        }
      },
    );

    //App is running in foreground
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Got a message whilst in the foreground!');
      print('Message body: ${message.notification?.body}');
      print('Message also contained a notification: ${message.data}');
      if (message.notification != null) {
        String msgTitle = "${message.notification!.title ?? ''}";
        String msgBody = "${message.notification!.body ?? ''}";

        // show a notification at top of screen.
        showSimpleNotification(
          InkWell(
            onTap: () {
              onClickRoute(message, "onMessage");
            },
            child: Text(
              msgTitle,
              maxLines: 1,
              softWrap: true,
            ),
          ),
          subtitle: InkWell(
            onTap: () {
              onClickRoute(message, "onMessage");
            },
            child: Text(
              msgBody,
              maxLines: 2,
              softWrap: true,
              overflow: TextOverflow.ellipsis,
            ),
          ),
          slideDismissDirection: DismissDirection.up,
          background: Color(0xffb497c4),
          duration: Duration(seconds: 3),
        );
      }
    });

    //App is in background but not killed
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      // log('A new onMessageOpenedApp event was published!${message.data}');
      onClickRoute(message, "onMessageOpenedApp");
    });
  }

onClickRoute(RemoteMessage message, String? f){
      print("////////////////////////$f///////////////////////////////");
      print(message);
}
  void setChannel() async {
    /// Create a [AndroidNotificationChannel] for heads up notifications
    AndroidNotificationChannel channel;

    /// Initialize the [FlutterLocalNotificationsPlugin] package.
    FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
    channel = const AndroidNotificationChannel(
      'high_importance_channel', // id
      'High Importance Notifications', // title
      description:
          'This channel is used for important notifications.', // description
      importance: Importance.high,
    );
    flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

    /// Create an Android Notification Channel.
    ///
    /// We use this channel in the `AndroidManifest.xml` file to override the
    /// default FCM channel to enable heads up notifications.
    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);

    /// Update the iOS foreground notification presentation options to allow
    /// heads up notifications.
    await FirebaseMessaging.instance
        .setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );
  }
}
devnta commented 1 year ago

I have faced same issue. It's always return null when opened app from terminated.

Woutwo commented 1 year ago

I have faced same issue. It's always return null when opened app from terminated.

Same problem here, getInitialMessage is always empty when opened from terminated state

russellwheatley commented 10 months ago

Keeping this issue on topic and responding to OP's issue. The getInitialMessage See documentation API is for use from a terminated state only. i.e. It should be used to check if a notification was used to open app from a terminated state. For notifications that are pressed and opened when app is in a background state, you should use onMessageOpenedApp.

This is why in our example app getInitialMessage is only called once unless the app is terminated: https://github.com/firebase/flutterfire/blob/master/packages/firebase_messaging/firebase_messaging/example/lib/main.dart#L182

I would encourage you to design your app in a manner where getInitialMessage is only called when app is starting up to find out if the app was opened via a notification. Closing out as this isn't an issue with FlutterFire.

AhmedLSayed9 commented 9 months ago

Same issue here using the official sample. It only occurs when doing a hot restart after receiving a message in the background. Re-opening the app has no issue.

suhail600 commented 9 months ago

Keeping this issue on topic and responding to OP's issue. The getInitialMessage See documentation API is for use from a terminated state only. i.e. It should be used to check if a notification was used to open app from a terminated state. For notifications that are pressed and opened when app is in a background state, you should use onMessageOpenedApp.

This is why in our example app getInitialMessage is only called once unless the app is terminated: https://github.com/firebase/flutterfire/blob/master/packages/firebase_messaging/firebase_messaging/example/lib/main.dart#L182

I would encourage you to design your app in a manner where getInitialMessage is only called when app is starting up to find out if the app was opened via a notification. Closing out as this isn't an issue with FlutterFire.

I am writing this comment to bring attention to this issue that several developers, including myself, have encountered and reported in the past. Despite numerous closed issues related to this matter, the problem remains unresolved. I kindly request your assistance in reevaluating the code samples provided and addressing the root cause of the routing issue.

Numerous developers, as evidenced by closed issues, have reported encountering the problem even when following all documentation criteria meticulously. It seems to persist particularly in scenarios where the app notification is consumed from a terminated state, and subsequently clearing the app from the background and reopening it results in unexpected routing. Furthermore, the getInitialMessage function appears to provide outdated notification data in such cases, resembling the same behavior observed when restarting the app.

I have thoroughly tested the cases mentioned in the detailed issue description and can confirm that the issue persists. Given the significant number of developers who have raised similar concerns in both the closed and open sections of the issue tracker, I believe urgent attention is required to investigate and provide a comprehensive fix for this issue.

I understand that issues may be complex and require careful consideration, but I urge you to reopen this matter and dedicate the necessary resources to thoroughly investigate and resolve the problem. Your prompt attention to this issue is crucial in ensuring a smooth and reliable experience for all developers using the code samples in question.

@russellwheatley @darshankawar

russellwheatley commented 9 months ago

Hey @suhail600, I circled back to this but I cannot reproduce. I'm not sure how you were able to get a message from getInitialMessage from debug mode. It doesn't return anything unless you open the app from a terminated state. debug mode does not work when the app is terminated, it has to be release mode.

I'm afraid your example code is broken. Show me using the firebase messaging example app (update this file: https://github.com/firebase/flutterfire/blob/master/packages/firebase_messaging/firebase_messaging/example/lib/main.dart) and provide the steps I need to take to see getInitialMessage continue to respond with messages that have already been consumed. Thanks.

suhail600 commented 9 months ago

Hey @suhail600, I circled back to this but I cannot reproduce. I'm not sure how you were able to get a message from getInitialMessage from debug mode. It doesn't return anything unless you open the app from a terminated state. debug mode does not work when the app is terminated, it has to be release mode.

I'm afraid your example code is broken. Show me using the firebase messaging example app (update this file: https://github.com/firebase/flutterfire/blob/master/packages/firebase_messaging/firebase_messaging/example/lib/main.dart) and provide the steps I need to take to see getInitialMessage continue to respond with messages that have already been consumed. Thanks.

as getInitialMessage says it should be called when app brought from terminated state and when its consumed it should be null.

Future<RemoteMessage?> getInitialMessage()
Type: Future<RemoteMessage?> Function()

package:firebase_messaging/firebase_messaging.dart

If the application has been opened from a terminated state via a [RemoteMessage] (containing a [Notification]), it will be returned, otherwise it will be null.

Once the [RemoteMessage] has been consumed, it will be removed and further calls to [getInitialMessage] will be null.

This should be used to determine whether specific notification interaction should open the app with a specific purpose (e.g. opening a chat message, specific screen etc).

but while checking with the example provided at -- https://github.com/firebase/flutterfire/blob/master/packages/firebase_messaging/firebase_messaging/example/lib/main.dart I was able reproduce the issue by the same code with added routing logic and extra code commenting , used data as route : nav_test_page and i have attached a video file zip and has console logs so that it shows the issue getInitialMessage is called multiple times on restarting after consumed from background state and when restarted the app getinitial message returns the consumed notification.

@russellwheatley could you please check this with the attached code sample.

video sample zip file:

noti test record.zip

code sample:

main.dart

import 'dart:async';
// import 'dart:convert';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

import 'package:firebasenoti/nav_test_page.dart';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

/// Working example of FirebaseMessaging.
/// Please use this in order to verify messages are working in foreground, background & terminated state.
/// Setup your app following this guide:
/// https://firebase.google.com/docs/cloud-messaging/flutter/client#platform-specific_setup_and_requirements):
///
/// Once you've completed platform specific requirements, follow these instructions:
/// 1. Install melos tool by running `flutter pub global activate melos`.
/// 2. Run `melos bootstrap` in FlutterFire project.
/// 3. In your terminal, root to ./packages/firebase_messaging/firebase_messaging/example directory.
/// 4. Run `flutterfire configure` in the example/ directory to setup your app with your Firebase project.
/// 5. Open `token_monitor.dart` and change `vapidKey` to yours.
/// 6. Run the app on an actual device for iOS, android is fine to run on an emulator.
/// 7. Use the following script to send a message to your device: scripts/send-message.js. To run this script,
///    you will need nodejs installed on your computer. Then the following:
///     a. Download a service account key (JSON file) from your Firebase console, rename it to "google-services.json" and add to the example/scripts directory.
///     b. Ensure your device/emulator is running, and run the FirebaseMessaging example app using `flutter run`.
///     c. Copy the token that is printed in the console and paste it here: https://github.com/firebase/flutterfire/blob/01b4d357e1/packages/firebase_messaging/firebase_messaging/example/lib/main.dart#L32
///     c. From your terminal, root to example/scripts directory & run `npm install`.
///     d. Run `npm run send-message` in the example/scripts directory and your app will receive messages in any state; foreground, background, terminated.
///  Note: Flutter API documentation for receiving messages: https://firebase.google.com/docs/cloud-messaging/flutter/receive
///  Note: If you find your messages have stopped arriving, it is extremely likely they are being throttled by the platform. iOS in particular
///  are aggressive with their throttling policy.
///
/// To verify that your messages are being received, you ought to see a notification appearon your device/emulator via the flutter_local_notifications plugin.
/// Define a top-level named handler which background/terminated messages will
/// call. Be sure to annotate the handler with `@pragma('vm:entry-point')` above the function declaration.
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  await setupFlutterNotifications();
  // showFlutterNotification(message);
  // If you're going to use other Firebase services in the background, such as Firestore,
  // make sure you call `initializeApp` before using other Firebase services.
  print('Handling a background message ${message.messageId}');
  print('background message title ${message.notification?.title}');
  print('background message body ${message.notification?.body}');
  print('background message data ${message.data}');
}

/// Create a [AndroidNotificationChannel] for heads up notifications
late AndroidNotificationChannel channel;

bool isFlutterLocalNotificationsInitialized = false;

Future<void> setupFlutterNotifications() async {
  if (isFlutterLocalNotificationsInitialized) {
    return;
  }
  channel = const AndroidNotificationChannel(
    'high_importance_channel', // id
    'High Importance Notifications', // title
    description:
        'This channel is used for important notifications.', // description
    importance: Importance.high,
  );

  flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

  /// Create an Android Notification Channel.
  ///
  /// We use this channel in the `AndroidManifest.xml` file to override the
  /// default FCM channel to enable heads up notifications.
  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  /// Update the iOS foreground notification presentation options to allow
  /// heads up notifications.
  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: true,
    badge: true,
    sound: true,
  );
  isFlutterLocalNotificationsInitialized = true;
}

// void showFlutterNotification(RemoteMessage message) {
//   RemoteNotification? notification = message.notification;
//   AndroidNotification? android = message.notification?.android;
//   if (notification != null && android != null && !kIsWeb) {
//     flutterLocalNotificationsPlugin.show(
//       notification.hashCode,
//       notification.title,
//       notification.body,
//       NotificationDetails(
//         android: AndroidNotificationDetails(
//           channel.id,
//           channel.name,
//           channelDescription: channel.description,
//           //  add a proper drawable resource to android, for now using
//           //  one that already exists in example app.
//           icon: 'launch_background',
//         ),
//       ),
//     );
//   }
// }

/// Initialize the [FlutterLocalNotificationsPlugin] package.
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  // Set the background messaging handler early on, as a named top-level function
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  if (!kIsWeb) {
    await setupFlutterNotifications();
  }

  runApp(const MessagingExampleApp());
}

// Entry point for the example application.
class MessagingExampleApp extends StatelessWidget {
  const MessagingExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Messaging Example App',
      theme: ThemeData.dark(),
      home: const Application(),
      // routes: {
      //   '/': (context) => const Application(),
      //   '/message': (context) => MessageView(),
      // },
    );
  }
}

// Crude counter to make messages unique
// int _messageCount = 0;

/// The API endpoint here accepts a raw FCM payload for demonstration purposes.
// String constructFCMPayload(String? token) {
//   _messageCount++;
//   return jsonEncode({
//     'token': token,
//     'data': {
//       'via': 'FlutterFire Cloud Messaging!!!',
//       'count': _messageCount.toString(),
//     },
//     'notification': {
//       'title': 'Hello FlutterFire!',
//       'body': 'This notification (#$_messageCount) was created via FCM!',
//     },
//   });
// }

/// Renders the example application.
class Application extends StatefulWidget {
  const Application({super.key});

  @override
  State<StatefulWidget> createState() => _Application();
}

class _Application extends State<Application> {
  String? _token;
  String? initialMessage;
  bool _resolved = false;

  @override
  void initState() {
    super.initState();

    FirebaseMessaging.instance.getInitialMessage().then(
      (value) {
        print('getInitialMessage called with data --> ${value?.data}');
        setState(
          () {
            _resolved = true;
            initialMessage = value?.data.toString();
            if (value?.data['route'] == 'nav_test_page') {
              if (initialMessage != null) {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (_) => NavTestPage(messageData: initialMessage!),
                  ),
                );
              }
            }
          },
        );
      },
    );

    // FirebaseMessaging.onMessage.listen(showFlutterNotification);

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('A new onMessageOpenedApp event was published!');
      print('----> ${message.data}');

      if (message.data['route'] == 'nav_test_page') {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (_) => NavTestPage(messageData: message.data.toString()),
          ),
        );
      }

      // Navigator.pushNamed(
      //   context,
      //   '/message',
      //   arguments: MessageArguments(message, true),
      // );
    });

    getToken();
  }

  Future<void> getToken() async {
    String? token = await FirebaseMessaging.instance.getToken();
    print('device token/id:  $token');
  }

  // Future<void> sendPushMessage() async {
  //   if (_token == null) {
  //     print('Unable to send FCM message, no token exists.');
  //     return;
  //   }

  //   try {
  //     await http.post(
  //       Uri.parse('https://api.rnfirebase.io/messaging/send'),
  //       headers: <String, String>{
  //         'Content-Type': 'application/json; charset=UTF-8',
  //       },
  //       body: constructFCMPayload(_token),
  //     );
  //     print('FCM request for device sent!');
  //   } catch (e) {
  //     print(e);
  //   }
  // }

  // Future<void> onActionSelected(String value) async {
  //   switch (value) {
  //     case 'subscribe':
  //       {
  //         print(
  //           'FlutterFire Messaging Example: Subscribing to topic "fcm_test".',
  //         );
  //         await FirebaseMessaging.instance.subscribeToTopic('fcm_test');
  //         print(
  //           'FlutterFire Messaging Example: Subscribing to topic "fcm_test" successful.',
  //         );
  //       }
  //       break;
  //     case 'unsubscribe':
  //       {
  //         print(
  //           'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test".',
  //         );
  //         await FirebaseMessaging.instance.unsubscribeFromTopic('fcm_test');
  //         print(
  //           'FlutterFire Messaging Example: Unsubscribing from topic "fcm_test" successful.',
  //         );
  //       }
  //       break;
  //     case 'get_apns_token':
  //       {
  //         if (defaultTargetPlatform == TargetPlatform.iOS ||
  //             defaultTargetPlatform == TargetPlatform.macOS) {
  //           print('FlutterFire Messaging Example: Getting APNs token...');
  //           String? token = await FirebaseMessaging.instance.getAPNSToken();
  //           print('FlutterFire Messaging Example: Got APNs token: $token');
  //         } else {
  //           print(
  //             'FlutterFire Messaging Example: Getting an APNs token is only supported on iOS and macOS platforms.',
  //           );
  //         }
  //       }
  //       break;
  //     default:
  //       break;
  //   }
  // }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Cloud Messaging'),
//         actions: <Widget>[
//           PopupMenuButton(
//             onSelected: onActionSelected,
//             itemBuilder: (BuildContext context) {
//               return [
//                 const PopupMenuItem(
//                   value: 'subscribe',
//                   child: Text('Subscribe to topic'),
//                 ),
//                 const PopupMenuItem(
//                   value: 'unsubscribe',
//                   child: Text('Unsubscribe to topic'),
//                 ),
//                 const PopupMenuItem(
//                   value: 'get_apns_token',
//                   child: Text('Get APNs token (Apple only)'),
//                 ),
//               ];
//             },
//           ),
//         ],
      ),
//       floatingActionButton: Builder(
//         builder: (context) => FloatingActionButton(
//           onPressed: sendPushMessage,
//           backgroundColor: Colors.white,
//           child: const Icon(Icons.send),
//         ),
//       ),
//       body: SingleChildScrollView(
//         child: Column(
//           children: [
//             MetaCard('Permissions', Permissions()),
//             MetaCard(
//               'Initial Message',
//               Column(
//                 children: [
//                   Text(_resolved ? 'Resolved' : 'Resolving'),
//                   Text(initialMessage ?? 'None'),
//                 ],
//               ),
//             ),
//             MetaCard(
//               'FCM Token',
//               TokenMonitor((token) {
//                 _token = token;
//                 return token == null
//                     ? const CircularProgressIndicator()
//                     : SelectableText(
//                         token,
//                         style: const TextStyle(fontSize: 12),
//                       );
//               }),
//             ),
//             ElevatedButton(
//               onPressed: () {
//                 FirebaseMessaging.instance
//                     .getInitialMessage()
//                     .then((RemoteMessage? message) {
//                   if (message != null) {
//                     Navigator.pushNamed(
//                       context,
//                       '/message',
//                       arguments: MessageArguments(message, true),
//                     );
//                   }
//                 });
//               },
//               child: const Text('getInitialMessage()'),
//             ),
//             MetaCard('Message Stream', MessageList()),
//           ],
//         ),
//       ),
    );
  }
}

/// UI Widget for displaying metadata.
class MetaCard extends StatelessWidget {
  final String _title;
  final Widget _children;

  // ignore: public_member_api_docs
  const MetaCard(this._title, this._children, {super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: const EdgeInsets.only(left: 8, right: 8, top: 8),
      child: Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              Container(
                margin: const EdgeInsets.only(bottom: 16),
                child: Text(_title, style: const TextStyle(fontSize: 18)),
              ),
              _children,
            ],
          ),
        ),
      ),
    );
  }
}

nav_test_page.dart

import 'package:flutter/material.dart';

class NavTestPage extends StatelessWidget {
  final String messageData;
  const NavTestPage({
    Key? key,
    required this.messageData,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Nav test page'),
      ),
      body: Center(
        child: Column(
          children: [
            Text(messageData),
          ],
        ),
      ),
    );
  }
}
ShahoodulHassan commented 8 months ago

Any solution or workaround for this issue so far?

bqubique commented 7 months ago

Same issue where the last message is re-delivered when app is opened from background or restarted.

class StubScreen extends StatefulWidget {
  const StubScreen({super.key});

  @override
  State<StubScreen> createState() => _StubScreenState();
}

class _StubScreenState extends State<StubScreen> {
  RemoteMessage? initialMessage;

  @override
  void initState() {
    super.initState();
    checkForMessage();
  }

  void checkForMessage() async {
    final notification = await FirebaseMessaging.instance.getInitialMessage();
    if (notification != null) {
      checkMessage(s);
    }

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('A new onMessageOpenedApp event was published!');

      checkMessage(message);
    });
  }

  void checkMessage(RemoteMessage? message) {
    log('${message?.data.toString()}');
    if (message != null && message.data['KEY'] != null) {
      Navigator.push(...);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold();
  }
}

This is the most similar approach to the github implementation of this case.

  firebase_core: ^2.15.1
  firebase_messaging: ^14.7.9

Replicated on a physical device (Pixel 7 pro).

flutter doctor -v output:

Details

[βœ“] Flutter (Channel stable, 3.13.9, on macOS 14.0 23A344 darwin-arm64, locale en-GB) β€’ Flutter version 3.13.9 on channel stable at /Users/blinqipa/development/flutter β€’ Upstream repository https://github.com/flutter/flutter.git β€’ Framework revision d211f42860 (4 months ago), 2023-10-25 13:42:25 -0700 β€’ Engine revision 0545f8705d β€’ Dart version 3.1.5 β€’ DevTools version 2.25.0 [βœ“] Android toolchain - develop for Android devices (Android SDK version 33.0.1) β€’ Android SDK at /Users/blinqipa/Library/Android/sdk β€’ Platform android-34, build-tools 33.0.1 β€’ Java binary at: /Applications/Android Studio Preview.app/Contents/jbr/Contents/Home/bin/java β€’ Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314) β€’ All Android licenses accepted. [βœ“] Xcode - develop for iOS and macOS (Xcode 15.0) β€’ Xcode at /Applications/Xcode.app/Contents/Developer β€’ Build 15A240d β€’ CocoaPods version 1.14.3 [βœ“] Chrome - develop for the web β€’ Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio (version unknown) β€’ Android Studio at /Applications/Android Studio Preview.app/Contents β€’ Flutter plugin can be installed from: πŸ”¨ https://plugins.jetbrains.com/plugin/9212-flutter β€’ Dart plugin can be installed from: πŸ”¨ https://plugins.jetbrains.com/plugin/6351-dart βœ— Unable to determine Android Studio version. β€’ Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314) [βœ“] VS Code (version 1.86.2) β€’ VS Code at /Applications/Visual Studio Code.app/Contents β€’ Flutter extension version 3.84.0 [βœ“] Connected device (4 available) β€’ Pixel 7 Pro (mobile) β€’ 2A181FDH30064X β€’ android-arm64 β€’ Android 14 (API 34) β€’ Blin’s iPhone (mobile) β€’ 00008020-000B598A3C41402E β€’ ios β€’ iOS 17.4 21E217 β€’ macOS (desktop) β€’ macos β€’ darwin-arm64 β€’ macOS 14.0 23A344 darwin-arm64 β€’ Chrome (web) β€’ chrome β€’ web-javascript β€’ Google Chrome 122.0.6261.94 [βœ“] Network resources β€’ All expected network resources are available. ! Doctor found issues in 1 category.

Edit: In case you will suggest me to rewrite the logic according to this example, I have tried with the following block of code:

@override
  void initState() {
    super.initState();

    FirebaseMessaging.instance.getInitialMessage().then(
          checkMessage,
        );

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('A new onMessageOpenedApp event was published!');

      checkMessage(message);
    });
  }

Same issue persists. Edit 2: Same issue persists with Flutter 3.19 and latest versions of firebase_core and firebase_messaging.

firebase_messaging: ^14.7.18
firebase_core: ^2.26.0
Updated flutter doctor -v output

[βœ“] Flutter (Channel stable, 3.19.2, on macOS 14.0 23A344 darwin-arm64, locale en-GB) β€’ Flutter version 3.19.2 on channel stable at /Users/blinqipa/development/flutter β€’ Upstream repository https://github.com/flutter/flutter.git β€’ Framework revision 7482962148 (8 days ago), 2024-02-27 16:51:22 -0500 β€’ Engine revision 04817c99c9 β€’ Dart version 3.3.0 β€’ DevTools version 2.31.1 [βœ“] Android toolchain - develop for Android devices (Android SDK version 33.0.1) β€’ Android SDK at /Users/blinqipa/Library/Android/sdk β€’ Platform android-34, build-tools 33.0.1 β€’ Java binary at: /Applications/Android Studio Preview.app/Contents/jbr/Contents/Home/bin/java β€’ Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314) β€’ All Android licenses accepted. [βœ“] Xcode - develop for iOS and macOS (Xcode 15.0) β€’ Xcode at /Applications/Xcode.app/Contents/Developer β€’ Build 15A240d β€’ CocoaPods version 1.14.3 [βœ“] Chrome - develop for the web β€’ Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio (version unknown) β€’ Android Studio at /Applications/Android Studio Preview.app/Contents β€’ Flutter plugin can be installed from: πŸ”¨ https://plugins.jetbrains.com/plugin/9212-flutter β€’ Dart plugin can be installed from: πŸ”¨ https://plugins.jetbrains.com/plugin/6351-dart βœ— Unable to determine Android Studio version. β€’ Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314) [βœ“] VS Code (version 1.86.2) β€’ VS Code at /Applications/Visual Studio Code.app/Contents β€’ Flutter extension version 3.84.0 [βœ“] Connected device (4 available) β€’ Pixel 7 Pro (mobile) β€’ 2A181FDH30064X β€’ android-arm64 β€’ Android 14 (API 34) β€’ Blin’s iPhone (mobile) β€’ 00008020-000B598A3C41402E β€’ ios β€’ iOS 17.4 21E217 β€’ macOS (desktop) β€’ macos β€’ darwin-arm64 β€’ macOS 14.0 23A344 darwin-arm64 β€’ Chrome (web) β€’ chrome β€’ web-javascript β€’ Google Chrome 122.0.6261.94 [βœ“] Network resources β€’ All expected network resources are available. ! Doctor found issues in 1 category.

russellwheatley commented 7 months ago

@bqubique - Are you seeing this behaviour when running the app in debug mode? This is confusing me, you won't be able to use the getInitialMessage() from debug mode as you need to terminate the app which will terminate the debug process. Could you provide clear steps to reproduce using the code you have already provided?

RuBaRuApp commented 3 months ago

@bqubique - Are you seeing this behaviour when running the app in debug mode? This is confusing me, you won't be able to use the getInitialMessage() from debug mode as you need to terminate the app which will terminate the debug process. Could you provide clear steps to reproduce using the code you have already provided?

The way to reproduce the issue is to do following: 1- Connect device 2- Run "flutter run --release" from terminal 3- Kill app from the device 4- Click on push notification 5- getInitialMessage() should remove the message once consumed, but if you call the getInitialMessage() again it returns the same RemoteMessage, but 3rd time it returns null

stickypan commented 1 week ago

@russellwheatley your statement about getInitialMessage() returning null in debug mode does not seem to be true. I experience the same issue that the OP described, and it also seems to be the same as #12411 My reproduction steps: 1) Launch the app in debug mode on Android 2) Put the app in background 3) Send the notification 4) Tap the notification on the device to open the app - getInitialMessage() is now called for the first time 5) Use device's back button to put the app in background 6) Tap the recents button on the device to view recently used apps and open the app from there (for me this is important, the issue does not happen if the app is simply reopened using app's icon) 7) getInitialMessage() is called again, with the exact same notification that should be already consumed at this point.

Please take a look at this - multiple devs have reported that issue, but it seems to be neglected.

Reproduced this both on a physical device and an emulator running Android 14. My dependencies:

- firebase_cloud_messaging 15.1.3
- firebase_core 3.6.0
yh-luo commented 3 days ago

I encountered a similar issue but a bit different: the notification is received in foreground. The app uses firebase_messaging the same way as shown in example:

The app was installed to the device by running in profile mode.

Reproduction steps

  1. Launch the app
  2. A notification is received while the app is in the foreground
  3. Use the back gesture to send the app to the background
  4. Use recent apps gesture to reopen the app
  5. Upon relaunching, the previous notification is displayed again.

Repeating steps 3 and 4 consistently results in step 5, where the same notification is received until the app is terminated and reopened.

Recording The video is recorded on Pixel 3A, Android 12. But the issue also happens on Android 14 devices.

https://github.com/user-attachments/assets/c276e41d-bb63-4bf0-b41f-fe982bf1fda9

Dependencies

firebase_core: 2.32.0
firebase_messaging: 14.9.4