Background
I have a gaming app, and I use Firebase Cloud Messaging (FCM) to send notifications to players when other players do certain things that may concern them. If a player taps the notification, I want them to be navigated to where the action happens, whether the app is currently in foreground, background or terminated. The information about where to navigate is sent with the FCM message.
The default mode for FCM is that when the app is terminated or in background, a remote notification is sent determined by the Cloud Function, but when the app is in foreground, no notification is shown. Instead, the remote message is handled in the app by a streamlistener, in which you can chose to show a local notification, which I do.
But what happens is that if the app is terminated or in background, I seem to get BOTH the remote notification and the local notification! And the biggest problem with that is that if I click the local notification while the app is terminated, I don't get navigated to where I'm supposed to be!...
The Problem
Navigation works from terminated IF I tap the remote notification. Then, the FirebaseMessaging.instance.getInitialMessage() is fired with the correct FCM message.
It also works if app is in foreground or background and I tap the local notification. Then, the onSelectNotification() of the local notification package is fired, with the FCM message as the payload.
But if I tap the local notification when the app is terminated, getInitialMessage() is fired with the initial message as null, and onSelectNotification() is not fired at all!... Thus, the app cannot find the right information needed for navigation.
What am I doing wrong, or how can I solve this?
My main:
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:navigation_history_observer/navigation_history_observer.dart';
import 'local_notifications.dart';
import 'fcm.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'my_firebase.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
MyFirebase.myFutureFirebaseApp = Firebase.initializeApp();
initializeFcm('');
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
LocalNotifications.initiate();
runApp(Blackbox());
}
My initializeFcm:
import 'package:blackbox/local_notifications.dart';
import 'package:blackbox/my_firebase.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
void initializeFcm(String token) async {
await MyFirebase.myFutureFirebaseApp;
FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
_firebaseMessaging.getInitialMessage().then((value) {
RemoteMessage? msg = value;
if (msg != null) openAction(msg); // This is where it navigates to the action
});
if (MyFirebase.authObject.currentUser != null) {
// An iOS thing... but can return a Future<NotificationSettings> on Android:
_firebaseMessaging.requestPermission(
sound: true,
//The rest as default
);
// I have no idea what the below does!
_firebaseMessaging.setAutoInitEnabled(true);
// If a message comes in while app in foreground:
FirebaseMessaging.onMessage.listen((remoteMsg) async {
if (remoteMsg.data.isNotEmpty) {
LocalNotifications.showNotification(
title: title,
notification: notification,
data: data,
category: 'GameHub',
description: 'New game hub events',
);
}
}, onError: (error) {
print('Error in onMessage: $error');
});
// Fired if notification opened app from background, not from terminated:
FirebaseMessaging.onMessageOpenedApp.listen((remoteMsg) {
openAction(remoteMsg);
});
}
}
My firebaseMessagingBackgroundHandler:
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage remoteMsg) async {
await Firebase.initializeApp();
if (remoteMsg.data.isNotEmpty) {
if (lots_of_nested_conditions) {
LocalNotifications.showNotification(
title: "Someone is playing your setup!",
notification: "Someone is playing your setup no ${remoteMsg.data['i']}.",
data: jsonEncode(remoteMsg.data),
category: 'GameHub',
description: 'New game hub events',
);
} else {
// If I am neither the player nor the sender of the setup:
print('A background data msg has come in. No local notification. Only maybe Cloud notification.');
}
}
}
My LocalNotifications.initiate(), featuring my onSelectNotification:
class LocalNotifications {
static FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
static void initiate() async {
var android = AndroidInitializationSettings('@drawable/ic_stat_name');
var iOS = IOSInitializationSettings();
var initSettings = InitializationSettings(android: android, iOS: iOS);
Future onSelectNotification(String? payload) async {
Map<String, dynamic>? msgData = jsonDecode(payload);
await MyFirebase.myFutureFirebaseApp;
if (MyFirebase.authObject.currentUser != null) {
navigateFromNotificationToFollowing(msgData: msgData);
}
}
flutterLocalNotificationsPlugin.initialize(initSettings, onSelectNotification: onSelectNotification);
}