6

I'm setting up push notifications via FCM and LocalPushNotifications, I was able to set it up in foreground state of app and in the background. In terminated state of the app, I do receive the notification, but when I press on it, no action happens, even though the foreground and background state of the app is working fine and navigates the user to the notification screen, in the terminated state, the app just opens and it doesn't navigate to the notification screen, only opens the main screen of the app. Since the device is not connected I can't see the error inside the console log, but when I start the app from the emulator, this is what I get on start:

I/flutter ( 3829): Got a message whilst in the terminated state!
I/flutter ( 3829): Message data: null

This is called inside the pushNotifications() method at FirebaseMessaging.instance.getInitialMessage().then()...

Here is the code with comments inside:

Logic for handling push notifications:

Future<void> pushNotifications() async {
  await Firebase.initializeApp();
  RemoteMessage initialMessage =
      await FirebaseMessaging.instance.getInitialMessage();

  if (initialMessage != null) {
    _handleMessage(initialMessage);
  }
/// THIS IS NOT WORKING, IT OPENS THE APP BUT DOESN'T NAVIGATE TO THE DESIRED SCREEN
  ///gives you the message on which user taps
  ///and it opened the app from terminated state
  FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage message) {
    LocalNotificationService.display(message);
    print('Got a message whilst in the terminated state!');
    print('Message data: ${message.data}');
    if (message != null) {  
      print('terminated state');
    }
  });

  ///forground work
  FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    LocalNotificationService.display(message);
    print('Got a message whilst in the foreground!');
    print('Message data: ${message.data}');
  });


///EVEN THOUGH IT IS SUPPOSED TO WORK LIKE THIS, I ONLY MANAGED TO MAKE IT WORK WITH BACKGROUND HANDLER, THIS METHOD NEVER TRIGGERS
  ///When the app is in background but opened and user taps
  ///on the notification
  FirebaseMessaging.onMessageOpenedApp.listen((message) {
    print('Got a message whilst in the background!');
    print('Message data: ${message.data}');
    _handleMessage(message);
    LocalNotificationService.display(message);
  });
}


///THIS HANDLES THE NOTIFICATIONS WHEN THE APP IS IN THE BACKGROUND
Future<void> _handleMessage(RemoteMessage message) async {
  await Firebase.initializeApp();
  if (message.data != null) {
    print('message handler');
    LocalNotificationService.display(message);/// ALL OF THESE CALLED FROM THE LocalNotificationService CLASS BELOW
  }
}
///MAIN METHOD, WHERE I INITIALIZE FIREBASE AND THE METHODES ABOVE(pushNotifications()), HANDLE THE MESSAGES WITH onBackgroundMessage(_handleMessage),
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  pushNotifications();
  var initializationSettingsAndroid =
      AndroidInitializationSettings("@mipmap/ic_launcher");
  var initializationSettingsIOS = IOSInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
      );
  await Firebase.initializeApp();
  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: true,
    badge: true,
    sound: true,
  );
  FirebaseMessaging.onBackgroundMessage(_handleMessage);
  runApp(MyApp());
}

My local notifications service class:

class LocalNotificationService {
  static final FlutterLocalNotificationsPlugin _notificationsPlugin =
      FlutterLocalNotificationsPlugin();

  static void initialize(BuildContext context) {
    final InitializationSettings initializationSettings =
        InitializationSettings(
            android: AndroidInitializationSettings("@mipmap/ic_launcher"));
    _notificationsPlugin.initialize(initializationSettings,
        onSelectNotification: (String payloadData) async {
     
      if (payloadData!= null) {
        
          Navigator.pushNamed(context, NotificationsScreen.id);
      }
    });
  }

  static void display(RemoteMessage message) async {
    try {
      final id = DateTime.now().millisecondsSinceEpoch ~/ 1000;

      final NotificationDetails notificationDetails = NotificationDetails(
          android: AndroidNotificationDetails(
        "push notifications",
        "push notifications",
        "push notifications",
        importance: Importance.max,
        priority: Priority.high,
      ));

      await _notificationsPlugin.show(
        id,
        'push notifications',
        'You have received a new push notification!',
        notificationDetails,
        payload: message.data['default'], // THIS IS NULL WHEN IN TERMINATED STATE OF APP
      );

    } on Exception catch (e) {
      print('exception: ' + e.toString());
    }
  }
}

So like I said, both foreground and background state is working and corresponding to the correct screen, but the terminated app state is not corresponding at all, but it does show the notification and opens the app when tapped on it.

Am I missing something? I mostly followed the documentation and some stuff on my own, but it is still not working as desired.

Any form of help is appreciated, thanks in advance!

GrandMagus
  • 600
  • 3
  • 12
  • 37
  • I have no help, except to say I'm in the same exact boat. Ive spent 4 days straight on this and can get one thing to work but another breaks. For example, I can get notifications to work in terminated state (as a data notification), but then there are double notifications when backgrounded, or some other weird variations. Im just posting to say that this flutter FCM module is overall seriously lacking right now. I plan to work on this for another day, If I can't solve it, abandon ship until the package is a bit more refined – Mark Jan 17 '22 at 18:20
  • Thanks @Mark for the help, I will be looking into it some more too, I still have the issue where I get notifications in terminated state but no payload data... I will post and update here if I find something. – GrandMagus Jan 18 '22 at 08:12

4 Answers4

4

I haven't been able to completely crack the problem you describe, and its very frustrating.

This is what I've figured out so far, and I'll put this here as it may help you:

There are 3 types of messages, data, notification and mixed.

If you use a pure data payload

  • The message is essentially silent.
  • onMessage does respond to data notifications when app foregrounded
  • onMessageOpenedApp does not trigger at all for data messages in any scenario
  • onBackgroundMessage is used to show a message while in terminated state or background state using local notifications
  • It is up to you (using the local notifications package) to deal with their clicks, using the onSelectNotification
    await flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: selectNotification
    );
  • You cannot use onSelectNotification in all these scenarios (as far as I've been able to yet determine *** this is a stumbling block for me right now

If you use a pure notification or mixed payload

  • The FCM package will display notifications for you when the app is backgrounded (but not foreground)

  • onMessage does respond to data when app foregrounded

  • onMessageOpenedApp WILL trigger when the notification is clicked in background state (but not for terminated state)

  • onBackgroundMessage will trigger, but unlike above, you should not use this to display a notification, as that will force a double notification (one processed by the FCM package and the other done by you manually)

  • Clicks, are dealt with by the package when the app is backgrounded, but by you when foregrounded. *** possibly also terminated, not sure yet.

As I mentioned, I've laid down some of the facts as Ive figure them out so far. Its very tricky and frustrating. Whats slowing me down immensely is that when using a mixed payload (what I've been using), while terminated the notifications either don't come at all, or come at their own pace (hours after they are sent).

If you make progress on this problem, let me know I think we are in the same boat...

Mark
  • 3,653
  • 10
  • 30
  • 62
  • Thanks again @Mark for the info! I also have an 'issue' which I stated in the question it self, I never trigger the `FirebaseMessaging.onMessageOpenedApp` method inside the `pushNotifications()` but only with `FirebaseMessaging.onBackgroundMessage(_handleMessage);` and I tried it like 100+ times (no joke :D) and it never triggers twice, but only through `onBackgroundMessage()`. Like I said, will update anything I find that may help us. Thanks again mate! – GrandMagus Jan 18 '22 at 08:17
  • do you know how to handle tap of notification when app is terminated I try getInitialMessage but it don't work for me – Dipak Ramoliya Apr 29 '22 at 12:17
1

@GrandMagus As luck has it I just got mine to work!! (getting the onMessageOpenedApp to trigger So I made a few changes and I'm not sure which one got it going.

  • I cleared all my caches and deleted the app and installed from scratch.
  • Edited AndroidManifest.xml to make changes as suggested by the documentation, in case you did do that they are: <intent-filter> <action android:name="FLUTTER_NOTIFICATION_CLICK" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>

<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="high_importance_channel" />

  • Changed my payload to be mixed data and notification. I included FLUTTER_NOTIFICATION_CLICK in my payload. I can explain that one further incase you don't know what that is. Its key to make your payload have at least the notification information. This forces the package to create the notification which will then respond to clicks. Do not handle creating the local push notification yourself in this scenario or you will get 2 notifications. If you are getting two, you will notice that the one generated by the package will in fact run the onMessageOpenedApp
  • I started testing my app in Release mode. Im wondering if the AndroidManifest.xml changes that allow for the heads up notifications only work if they are placed in each .xml file. In this case, I was editing the release file but testing on the debug version
Dharman
  • 30,962
  • 25
  • 85
  • 135
Mark
  • 3,653
  • 10
  • 30
  • 62
  • Sorry for the late reply, had some issues with the BE, I tried adding these to my AndroidManifest.xml file but it still work the same, I will try to change the payload to be mixed, seems like a last resort. :D (*cries in corner) – GrandMagus Jan 23 '22 at 14:07
  • @GrandMagus No problem. I will say that I finally got mine to work dandy, so best of luck. let me know if I can help any – Mark Jan 23 '22 at 16:48
1

I had some similar issue and finally could solve it with using Navigator.pushNamed() in a different way. Although I managed the handling of notifications a little bit different to you. Here is what I did:

  • create a file route_generator.dart -> this will allow you to handle routing through your app and passing i.e. payloads to another screen:
class RouteGenerator {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    final args = settings.arguments;

    switch (settings.name) {
      // my homescreen
      case '/':
        return MaterialPageRoute(
            builder: (context) => const ShoppingItemListScreen());
      // my target screen to reach from notifications
      case '/add':
        return MaterialPageRoute(
            builder: (context) => AddShoppingItemScreen(
                  // my data payload from notifications I'm passing to the screen
                  shoppingItem: args,
                ));
      default:
        return _errorRoute();
    }
  }

  static Route<dynamic> _errorRoute() {
    return MaterialPageRoute(builder: (context) {
      return Scaffold(
        appBar: AppBar(
          title: const Text('ERROR'),
          centerTitle: true,
        ),
        body: const Center(
          child: Text('Page not found!'),
        ),
      );
    });
  }
}
  • use a GlobalKey in your main.darts StatelessWidget:
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  // this is your GlobalKey
  static final GlobalKey<NavigatorState> navigatorKey =
      GlobalKey(debugLabel: 'Main Navigator');

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        navigatorKey: navigatorKey,
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        onGenerateRoute: RouteGenerator.generateRoute,
    );
  }
}
  • use GlobalKey in an async method handling the interaction with notifications:
void onTapLocalNotification(NotificationResponse notificationResponse) async {
    final String? payload = notificationResponse.payload;
    debugPrint(payload);
    await MyApp.navigatorKey.currentState!.pushNamed(
      '/add',
      arguments: ShoppingItem(name: payload!),
    );
}
  • call onTapLocalNotification:
final FlutterLocalNotificationsPlugin notificationsPlugin =
      FlutterLocalNotificationsPlugin();

Future<void> launchNotification() async {
    const InitializationSettings initializationSettings =
        InitializationSettings(
      android: AndroidInitializationSettings("@drawable/ic_launcher"),
      iOS: DarwinInitializationSettings(
        requestSoundPermission: true,
        requestAlertPermission: true,
        requestBadgePermission: true,
      ),
    );
    // getting information if notification launched the app (from terminated state)
    final NotificationAppLaunchDetails? notificationAppLaunchDetails =
        await notificationsPlugin.getNotificationAppLaunchDetails();
    final didNotificationLaunchApp =
        notificationAppLaunchDetails?.didNotificationLaunchApp ?? false;

    if (didNotificationLaunchApp) {
      var payload = notificationAppLaunchDetails!.notificationResponse;
      onTapLocalNotification(payload!);
    } else {
      await notificationsPlugin.initialize(
        initializationSettings,
        onDidReceiveNotificationResponse: onTapLocalNotification,
        onDidReceiveBackgroundNotificationResponse: onTapLocalNotification,
      );
    }
}

I hope this will help you in case you are still facing the issue.

0

You can use flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails() to get payload from notification when app is in terminated state.

Imam Muhtadi
  • 11
  • 1
  • 3