Flutter应用分离Firebase Cloud Messaging至独立文件后无法获取通知数据
问题分析与解决方案
首先,咱们来拆解下你遇到的两个核心问题:
initState里直接用await是非法的:initState方法不能标记为async,这会违反Flutter框架的生命周期规则。- 通知数据无法传递的本质原因:你的
initialise方法返回的initMessage是初始默认值,因为_fcm.configure里的onMessage/onLaunch/onResume都是异步回调——当这些回调触发并给initMessage赋值时,initialise早就已经返回了初始字符串,所以main.dart里拿不到真实的通知数据。
另外那个Fatal: failed to find callback是旧版FCM SDK的小bug,升级SDK版本大概率能解决,但这不是影响你获取数据的核心问题。
下面是具体的修复方案,我们用Stream来实现通知数据的实时传递,这是处理异步回调数据的标准方式:
1. 重构push_notification_service.dart
我们添加一个StreamController来广播通知数据,让外部可以监听数据变化:
import 'dart:io'; import 'dart:async'; import 'package:firebase_messaging/firebase_messaging.dart'; class PushNotificationService { final FirebaseMessaging _fcm; final StreamController<String> _notificationController = StreamController<String>.broadcast(); // 对外暴露Stream,供外部监听通知标题 Stream<String> get notificationTitleStream => _notificationController.stream; PushNotificationService(this._fcm); Future<void> initialise() async { if (Platform.isIOS) { await _fcm.requestNotificationPermissions(const IosNotificationSettings()); } _fcm.configure( onMessage: (Map<String, dynamic> message) async { print("Message -Foreground- received: $message"); final title = message['notification']['title']; _notificationController.add(title); }, onLaunch: (Map<String, dynamic> message) async { print("Message -Closed- received: $message"); final title = message['notification']['title']; _notificationController.add(title); }, onResume: (Map<String, dynamic> message) async { print("Message -Background- received: $message"); final title = message['notification']['title']; _notificationController.add(title); }, ); // 可选:处理冷启动时的初始通知 final initialMessage = await _fcm.getInitialMessage(); if (initialMessage != null) { final title = initialMessage['notification']['title']; _notificationController.add(title); } } // 页面销毁时关闭StreamController,避免内存泄漏 void dispose() { _notificationController.close(); } }
2. 修改main.dart监听通知数据
现在我们在initState中初始化服务,并监听Stream来更新title:
import 'package:flutter/material.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import './push_notification_service.dart'; void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { final FirebaseMessaging _fcm = FirebaseMessaging.instance; // 新版FCM推荐写法,旧版可改回FirebaseMessaging() late PushNotificationService _notificationService; String title = "Notif Title"; String helperText = "Notif Text"; StreamSubscription? _notificationSubscription; @override void initState() { super.initState(); _initNotificationService(); } // 用单独的异步函数包裹初始化逻辑,避免在initState里直接用async void _initNotificationService() async { _notificationService = PushNotificationService(_fcm); await _notificationService.initialise(); // 监听通知标题变化,实时更新UI _notificationSubscription = _notificationService.notificationTitleStream.listen((newTitle) { setState(() { title = newTitle; }); }); } @override void dispose() { _notificationSubscription?.cancel(); _notificationService.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: Center(child: Text(title)), ), ); } }
额外说明
- 我把
FirebaseMessaging _fcm = FirebaseMessaging();改成了FirebaseMessaging.instance,这是FCM最新版本的推荐写法,如果你用的是旧版SDK,可以改回原来的写法。 - 新增了
dispose方法来关闭Stream和取消订阅,这是避免内存泄漏的关键步骤。 - 加入了
getInitialMessage的处理,确保冷启动点击通知时也能获取到通知数据。
这样修改后,无论是前台、后台还是冷启动收到通知,title变量都会实时更新并刷新UI,你之前遇到的问题就解决啦。
内容的提问来源于stack exchange,提问作者usersina
相关产品推荐
相关产品推荐

