使用 FirebaseMessaging.instance.getInitialMessage() 处理终止状态时接收空值

Receiving null value when using FirebaseMessaging.instance.getInitialMessage() for handling terminated state

我正在使用 Flutter 开发一个应用程序,我正在使用带有 firebase_messaging: ^10.0.4 Flutter 插件的 FCM 实现推送通知:

我在移动应用程序处于终止状态时使用 Firebase 在移动应用程序上发送通知。为了让通知处于终止状态,我使用 FirebaseMessaging.instance.getInitialMessage() 来处理通知的点击。当用户点击通知时,他们将被路由到特定屏幕(显示已传递的消息)。

问题是我在终止状态的移动应用程序中收到通知,但是当我点击通知时,我没有被路由到我从 Firebase 传入的特定屏幕并且 FirebaseMessaging.instance.getInitialMessage() 值为在消息中获取空值。

如果有人对此有任何想法,请告诉我。

main.dart

checkFirebase() async {
  await Firebase.initializeApp();

  // Set the background messaging handler early on, as a named top-level function
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  if (!kIsWeb) {
    channel = const AndroidNotificationChannel(
      'high_importance_channel', // id
      'High Importance Notifications', // title
      description: 'This channel is used for important notifications.',
      // description
      importance: Importance.max,
    );

    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.showFrontNotification
    await FirebaseMessaging.instance
        .setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );

    FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
  }
}


 
class _ProcessAppState extends State<ProcessApp> {
  Future<void> _initializeFuture;

  Future<void> _initializeServices() async {
    await Firebase.initializeApp();
    await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
    var dir = await getApplicationDocumentsDirectory();
    Hive.init(dir.path);
    // pass all uncaught errors to crashlytics
    Function originalOnError = FlutterError.onError;
    FlutterError.onError = (FlutterErrorDetails errorDetails) async {
      await FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
      originalOnError(errorDetails);
    };
    // Set the background messaging handler early on, as a named top-level function
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    if (!kIsWeb) {
      channel = const AndroidNotificationChannel(
        'high_importance_channel', // id
        'High Importance Notifications', // title
        description: 'This channel is used for important notifications.',
        // description
        importance: Importance.max,
      );

/*
      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,
      );
    }
  }

  @override
  void initState() {
    super.initState();
    _initializeFuture = _initializeServices();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: FutureBuilder(
          future: _initializeFuture,
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              reportError(
                  error: snapshot.error, stackTrace: snapshot.stackTrace);
              return Center(
                child: Text(context.translateText(key: "general_error")),
              );
            }
            if (snapshot.connectionState == ConnectionState.done) {
              return MyApp();
            }
            return progressBar();
          },
        ),
      ),
    );
  }
}



 @override
  Widget build(BuildContext context) {
   
    return AnnotatedRegion<SystemUiOverlayStyle>(
      value: SystemUiOverlayStyle(
        statusBarColor: gradientTopColor,
        statusBarBrightness: Brightness.dark,
      ),
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
            primarySwatch: primaryColor,
            backgroundColor: whiteColor,
            fontFamily: 'Roboto'),
        supportedLocales: [
          Locale('en'),
          Locale('ta'),
          Locale('ml'),
          Locale('kn'),
          Locale('te'),
        ],
        localizationsDelegates: [
          // for our own localizations
          AppLocalizations.delegate,
          // localizations for all material widgets provided
          GlobalMaterialLocalizations.delegate,
          // localizations for all cupertino widgets provided
          DefaultCupertinoLocalizations.delegate,
          // for rtl, ltr text directions
          GlobalWidgetsLocalizations.delegate,
        ],
        locale: _locale,
        localeResolutionCallback: (deviceLocale, supportedLocales) {
          try {
            return Locale(defaultLang);
          } catch (e) {
            print(e);
            return Locale("en");
          }
        },
        // navigation analytics reporting
        // navigatorObservers: <NavigatorObserver>[observer],
        home: NotificationMessageHandler(child: LauncherScreen()),
        
        builder: EasyLoading.init(),
      ),
    );
  }

message_handler.dart

class _NotificationMessageHandlerState extends State<NotificationMessageHandler>
    with AfterLayoutMixin<NotificationMessageHandler> {

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

    // _checkForUpdate();

    var initializationSettingsAndroid =
        AndroidInitializationSettings("@mipmap/ic_launcher");
    var initializationSettings =
        InitializationSettings(android: initializationSettingsAndroid);

    flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: (payload) async {
      log("payload : $payload", name: "onSelectNotification");
      handleNotificationClick(context, jsonDecode(payload));
    });

    FirebaseMessaging.instance
        .getInitialMessage()
        .then((RemoteMessage initialMessage) async {
      log("message : $initialMessage", name: "getInitialMessage");
      handleNotificationClick(context, jsonDecode(initialMessage.toString()));
    });

    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print(message);
      showFrontNotification(message);

    });

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      handleNotificationClick(context, message.data);
    });


  }

 

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

当您的应用程序终止并且您想导航到另一个屏幕时,您需要来自 MaterialApp 的 navigatorKey 的上下文以及您实际想去的关键字,我们将"click_action" FCM 请求正文中的键。

我建议您在单独的文件中处理 Firebase 消息传递代码。

fcm_service.dart 此文件包含前台、后台和应用程序终止后的路由导航器,还处理带有图像的通知。

import 'dart:convert';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;

import '../main.dart';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

const AndroidNotificationChannel channel = AndroidNotificationChannel(
  'custom_notification_channel_id',
  'Notification',
  description: 'notifications from Your App Name.',
  importance: Importance.high,
);

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

void setupFcm() {
  var initializationSettingsAndroid = const AndroidInitializationSettings('@mipmap/ic_launcher');
  var initializationSettingsIOs = const IOSInitializationSettings();
  var initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: initializationSettingsIOs,
  );

  //when the app is in foreground state and you click on notification.
  flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onSelectNotification: (String payload) {
        if (payload != null) {
          Map<String, dynamic> data = json.decode(payload);
          goToNextScreen(data);
        }
  });

  //When the app is terminated, i.e., app is neither in foreground or background.
  FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage message) {
    //Its compulsory to check if RemoteMessage instance is null or not.
    if (message != null) {
      goToNextScreen(message.data);
    }
  });

  //When the app is in the background, but not terminated.
  FirebaseMessaging.onMessageOpenedApp.listen((event) {
      goToNextScreen(event.data);
    },
    cancelOnError: false,
    onDone: () {},
  );

  FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
      RemoteNotification notification = message.notification;
      AndroidNotification android = message.notification?.android;
      if (notification != null && android != null) {
        if (android.imageUrl != null && android.imageUrl.trim().isNotEmpty) {
          final String largeIcon = await _base64encodedImage(
            android.imageUrl,
          );

          final BigPictureStyleInformation bigPictureStyleInformation =
          BigPictureStyleInformation(
            ByteArrayAndroidBitmap.fromBase64String(largeIcon),
            largeIcon: ByteArrayAndroidBitmap.fromBase64String(largeIcon),
            contentTitle: notification.title,
            htmlFormatContentTitle: true,
            summaryText: notification.body,
            htmlFormatSummaryText: true,
            hideExpandedLargeIcon: true,
          );

          flutterLocalNotificationsPlugin.show(
            notification.hashCode,
            notification.title,
            notification.body,
            NotificationDetails(
              android: AndroidNotificationDetails(
                channel.id,
                channel.name,
                channelDescription: channel.description,
                icon: 'custom_notification_icon',
                color: primaryColor,
                importance: Importance.max,
                priority: Priority.high,
                largeIcon: ByteArrayAndroidBitmap.fromBase64String(largeIcon),
              styleInformation: bigPictureStyleInformation,
              ),
            ),
            payload: json.encode(message.data),
          );
        }
        else {
          flutterLocalNotificationsPlugin.show(
            notification.hashCode,
            notification.title,
            notification.body,
            NotificationDetails(
              android: AndroidNotificationDetails(
                channel.id,
                channel.name,
                channelDescription: channel.description,
                icon: 'custom_notification_icon',
                color: primaryColor,
                importance: Importance.max,
                priority: Priority.high,
              ),
            ),
            payload: json.encode(message.data),
          );
        }
      }
    });

}

Future<void> deleteFcmToken() async {
  return await FirebaseMessaging.instance.deleteToken();
}

Future<String> getFcmToken() async {
  String token = await FirebaseMessaging.instance.getToken();
  return Future.value(token);
}

void goToNextScreen(Map<String, dynamic> data) {
  if (data['click_action'] != null) {
    switch (data['click_action']) {
      case "first_screen":
        navigatorKey.currentState.pushNamed(FirstScreen.routeName,);
        break;
      case "second_screen":
        navigatorKey.currentState.pushNamed(SecondScreen.routeName,);
        break;
      case "sample_screen":
        navigatorKey.currentState.pushNamed(SampleScreen.routeName,);
    }
    return;
  }
  //If the payload is empty or no click_action key found then go to Notification Screen if your app has one.
  navigatorKey.currentState.pushNamed(NotificationPage.routeName,);
}

Future<String> _base64encodedImage(String url) async {
  final http.Response response = await http.get(Uri.parse(url));
  final String base64Data = base64Encode(response.bodyBytes);
  return base64Data;
}

main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);

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

  runApp(const MyApp());
}

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

class MyApp extends StatefulWidget {
  const MyApp({Key key}) : super(key: key);
    
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: navigatorKey,
      onGenerateRoute: //Define your named routes.
    );
  }
}

此外,您还需要定义默认通知渠道id,以及可选的默认通知图标,默认通知颜色

AndroidManifest.xml

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

   <!-- Set custom default icon. This is used when no icon is set for incoming notification messages. -->
   <meta-data
       android:name="com.google.firebase.messaging.default_notification_icon"
       android:resource="@drawable/custom_notification_icon" />

   <!-- Set color used with incoming notification messages. This is used when no color is set for the incoming notification message.  -->
   <meta-data
       android:name="com.google.firebase.messaging.default_notification_color"
       android:resource="@color/notification_icon_color" />
</application>

您还可以检查示例 FCM HTTP 请求以处理其中的 "data" key JsonObject 和 "click_action" .

URL: https://fcm.googleapis.com/fcm/send
Request Method: POST
Header: {
  "Authorization": "key={value1}",
  "Sender": "id={value2}",
}
Request Body: {
    "registration_ids": [
        "exwlH32_S0il4ky4ZRXCrg:APA91bHp4kL-IJmtHRGFcQlhUauEY1ZiqZFfWsDkWqsB-yHDUzRVx63e8ehSirUTbSg6NqMqAAfcW16tk4dgs-NtTcCVShipGt9JWIJK_r8b4ldqFYGhzZcNF0VTiVKWzWkRQQIncCoE"
    ],
    "notification": {
        "title": "Wear Mask",
        "body": "Maintain social distance",
        "image": "https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1",
        "imageUrl": "https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1",
        "sound": "default"
    },
    "data": {
        "click_action": "sample_screen",
        "custom_key": "custom_value",
        "image": "https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1",
        "imageUrl": "https://repository-images.githubusercontent.com/31792824/fb7e5700-6ccc-11e9-83fe-f602e1e1a9f1"
    }
}

注意:registration_ids键在列表中只取 1000 个值。

"data" JsonObject 中,您可以定义自定义键值对,这将派上用场。例如,你想打开一个特定的屏幕,比方说 event_screen.dart,你需要通过事件 ID 从服务器获取事件详细信息。因此,您可以相应地准备 "data" 对象

"data": {
        "click_action": "event_screen",
        "event_id": "23"
    }