Flutter 基于SharedPreferences实现FCM通知持久化存储与展示
通知数据持久化实现方案
现有代码的核心问题
- 存储逻辑类型不匹配:SharedPreferences的
setStringList要求传入List<String>类型,原代码把notifList.toString()(单个字符串)直接传入,不仅无法正常写入,还会触发类型报错 - 存储时机完全错误:在
initState阶段调用存储方法时,notifList还是初始空列表,此时写入会直接覆盖掉本地已保存的历史通知数据 - 缺少数据读取流程:页面初始化时没有从本地读取历史通知的逻辑,
notifList永远是空的,就算存了数据也渲染不出来 - 复杂结构未做序列化:SharedPreferences不支持直接存Map、嵌套列表这类复杂结构,直接toString转出来的字符串无法反序列化回原数据结构
具体实现步骤
- 引入序列化依赖:用Dart自带的
dart:convert库处理JSON转换,不需要额外添加第三方依赖 - 实现统一的存储、读取方法
把通知列表统一序列化成JSON字符串存入SharedPreferences,读取时再反序列化回Map列表结构,代码如下:import 'dart:convert'; // 从本地读取历史通知列表 Future<void> _loadSavedNotifications() async { final sharePref = await SharedPreferences.getInstance(); final savedJson = sharePref.getString('push_notifications') ?? ''; if (savedJson.isEmpty) { setState(() => notifList = []); return; } final List<dynamic> decodedList = jsonDecode(savedJson); setState(() { notifList = decodedList .map((item) => Map<String, String?>.from(item as Map)) .toList(); }); } // 把当前通知列表持久化到本地 Future<void> _saveNotifications() async { final sharePref = await SharedPreferences.getInstance(); // 兼容空安全,把原Map转成可序列化的Map结构 final serializedList = notifList .map((map) => Map<String, String?>.from(map)) .toList(); final jsonStr = jsonEncode(serializedList); // 直接以JSON字符串形式存储,不需要用setStringList await sharePref.setString('push_notifications', jsonStr); } - 修正FCM消息监听的触发逻辑
不管是前台收到通知、冷启动点通知进入、后台点通知进入,只要把新通知加入notifList,就立刻触发存储方法,避免内存数据丢失:Future<dynamic> fcmMessageReceiver() async { // 冷启动打开app的场景 FirebaseMessaging.instance.getInitialMessage().then((value) { if (value?.notification != null) { notific = { 'title': value!.notification!.title, 'body': value.notification!.body }; notifList.add(notific); setState(() {}); _saveNotifications(); } }); // 前台收到消息的场景 FirebaseMessaging.onMessage.listen((RemoteMessage message) { if (message.notification != null) { notific = { 'title': message.notification!.title, 'body': message.notification!.body }; notifList.add(notific); setState(() { title = message.notification!.title; body = message.notification!.body; }); // 列表更新后立刻存储 _saveNotifications(); print('MENSAGEM: $notific'); } }); // 后台点击通知打开app的场景 FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { if (message.notification != null) { notific = { 'title': message.notification!.title, 'body': message.notification!.body }; notifList.add(notific); setState(() {}); _saveNotifications(); } }); } - 调整initState的执行顺序
打开页面时先加载本地存的历史通知,再启动FCM消息监听,不要在initState里直接执行存储逻辑:@override void initState() { super.initState(); _loadSavedNotifications().then((_) => fcmMessageReceiver()); }
实用优化建议
- 限制通知存储条数:如果长期累积通知列表过长,会导致JSON字符串过大,拖慢SharedPreferences的读写速度,可以在每次加新通知后判断长度,比如只保留最近50条,超过就删掉最早的条目:
// 加完新通知后执行 if (notifList.length > 50) notifList.removeAt(0); - 做空安全适配:FCM推送的通知标题、内容都是可空值,转Map的时候不要硬转成非空字符串,避免遇到无内容的推送时直接崩溃
- 读取时机优化:如果通知页面不是打开app就必须展示的页面,可以等用户点击进入通知页时再触发本地数据读取,减少app启动时的加载耗时
内容的提问来源于stack exchange,提问作者Kaya Ryuuna
相关产品推荐
相关产品推荐

