Flutter使用ChangeNotifier实现推送消息列表无法更新重建页面问题
我想实现一个推送消息列表,但找不到更新列表并重建HomePage的方法。核心问题出在这两行代码:
final MessageModel messageModel = MessageModel(); messageModel.addMessage(message);
main() 代码
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:notification_exemple_4/version/v1/pages/home_page.dart'; import 'package:notification_exemple_4/version/v1/pages/notification_page.dart'; import 'package:provider/provider.dart'; import 'api/firebase_api.dart'; import 'firebase_options.dart'; final navigatorKey = GlobalKey<NavigatorState>(); class MessageModel extends ChangeNotifier { List<Widget> _messaging = []; List<Widget> get messaging => _messaging; void addMessage(RemoteMessage remoteMessage) { messaging.add(Text(remoteMessage.messageId.toString())); notifyListeners(); } } final MessageModel messageModel = MessageModel(); @pragma('vm:entry-point') Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message,) async { // 如果要在后台使用其他Firebase服务,比如Firestore, // 确保在使用前调用`initializeApp` await Firebase.initializeApp(); print("Handling a background message: ${message.messageId}"); } Future<void> main() async{ WidgetsFlutterBinding.ensureInitialized(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); FirebaseMessaging.onMessage.listen((RemoteMessage message) { print('Got a message whilst in the foreground!'); print('Message data: ${message.data}'); if (message.notification != null) { messageModel.addMessage(message); print("tp failed"); /*navigatorKey.currentState?.pushNamed( '/home', arguments: message, );*/ print('Message also contained a notification: ${message.notification}'); } }); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await FirebaseApi().iniNotifications(); runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => MessageModel()), ], child: const MyApp(), ) ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: const HomePage(), navigatorKey: navigatorKey, routes: { "/home": (context) => const HomePage(), "/notification_screen": (context) => const NotificationPage(), }, ); } }
我用MessageModel存储消息列表做测试展示,同时通过MultiProvider注册了MessageModel的Provider,但又声明了全局的MessageModel实例,在FirebaseMessaging的onMessage监听里调用该实例的addMessage方法。
HomePage() 代码
import 'package:flutter/material.dart'; import 'package:notification_exemple_4/version/v1/main.dart'; import 'package:provider/provider.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { @override Widget build(BuildContext context) { print(messageModel.messaging); return Scaffold( appBar: AppBar(title: const Text("Home Page"),), body: SafeArea( child: ListView( children: context.watch<MessageModel>().messaging, ) ), ); } }
HomePage里调用消息列表用ListView展示,已经把read()改成watch()了。我尝试用Provider包,但onMessage.listen()里没有context,拿不到Provider实例,导致无法触发HomePage重建。
解决方案
问题根源在于你维护了两个独立的MessageModel实例:
- 全局声明的
messageModel实例,你在onMessage监听中调用它的addMessage - MultiProvider创建的全新MessageModel实例,HomePage通过
context.watch<MessageModel>()监听的是这个实例
两者完全无关,所以全局实例的notifyListeners()无法触发HomePage的重建。
修复步骤:
删除全局的MessageModel实例,移除main里的
final MessageModel messageModel = MessageModel();声明调整监听时机,获取Provider实例
把onMessage监听移到runApp之后,通过全局navigatorKey获取上下文来拿到Provider实例:Future<void> main() async{ WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await FirebaseApi().iniNotifications(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => MessageModel()), ], child: const MyApp(), ) ); // 延迟确保Provider初始化完成 Future.microtask(() { FirebaseMessaging.onMessage.listen((RemoteMessage message) { print('Got a message whilst in the foreground!'); print('Message data: ${message.data}'); if (message.notification != null) { // 通过navigatorKey获取上下文,操作Provider管理的实例 final messageModel = Provider.of<MessageModel>(navigatorKey.currentContext!, listen: false); messageModel.addMessage(message); print('Message also contained a notification: ${message.notification}'); } }); }); }优化MessageModel设计
不要直接存储Widget,改为存储消息数据,在HomePage中根据数据构建UI,更符合状态管理最佳实践:class MessageModel extends ChangeNotifier { List<RemoteMessage> _messages = []; List<RemoteMessage> get messages => _messages; void addMessage(RemoteMessage remoteMessage) { _messages.add(remoteMessage); notifyListeners(); } }同步修改HomePage:
@override Widget build(BuildContext context) { final messages = context.watch<MessageModel>().messages; return Scaffold( appBar: AppBar(title: const Text("Home Page"),), body: SafeArea( child: ListView.builder( itemCount: messages.length, itemBuilder: (context, index) { return Text(messages[index].messageId.toString()); }, ) ), ); }移除HomePage对全局实例的引用
删除print(messageModel.messaging);这行代码,避免引用不存在的全局实例。
修改完成后,onMessage监听中操作的就是Provider管理的同一个MessageModel实例,调用addMessage后notifyListeners()会触发HomePage的重建,消息列表就能正常更新。
内容的提问来源于stack exchange,提问作者Mr. Munsch

