You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何将Firebase RemoteMessage编码为JSON存储

方案可行性判断

你的实现思路完全正确。
如果只是存储少量推送消息(比如最新一条推送、和未读计数关联的消息),用SharedPreferences存储序列化后的JSON字符串是成本最低、实现最简便的方案,不需要引入额外本地数据库依赖。只有当你需要存储大量历史推送、需要做消息查询筛选时,才需要考虑换成Hive、SQFlite这类本地持久化方案。

你现有代码的核心问题是:直接将RemoteNotification类型的notification字段放入序列化Map,该类型无法直接被jsonEncode处理,会导致序列化失败。


完整可复用的序列化/反序列化扩展

以下是补全的全字段序列化逻辑,同时配套了反序列化方法,存完之后可以直接从SharedPreferences读取出JSON字符串转回RemoteMessage对象使用:

extension RemoteMessageJsonExtension on RemoteMessage {
  // 序列化:RemoteMessage转可Json编码的Map
  Map<String, dynamic> toJson() {
    return {
      'senderId': senderId,
      'category': category,
      'collapseKey': collapseKey,
      'contentAvailable': contentAvailable,
      'data': data,
      'from': from,
      'messageId': messageId,
      'messageType': messageType,
      'mutableContent': mutableContent,
      // 递归序列化notification字段
      'notification': notification?.toJson(),
      'sentTime': sentTime?.millisecondsSinceEpoch,
      'threadId': threadId,
      'ttl': ttl,
    };
  }

  // 反序列化:Map转RemoteMessage对象
  static RemoteMessage fromJson(Map<String, dynamic> json) {
    return RemoteMessage(
      senderId: json['senderId'],
      category: json['category'],
      collapseKey: json['collapseKey'],
      contentAvailable: json['contentAvailable'],
      data: Map<String, String>.from(json['data'] ?? {}),
      from: json['from'],
      messageId: json['messageId'],
      messageType: json['messageType'],
      mutableContent: json['mutableContent'],
      notification: json['notification'] != null 
          ? RemoteNotificationExtension.fromJson(json['notification']) 
          : null,
      sentTime: json['sentTime'] != null 
          ? DateTime.fromMillisecondsSinceEpoch(json['sentTime']) 
          : null,
      threadId: json['threadId'],
      ttl: json['ttl'],
    );
  }
}

// RemoteNotification配套序列化扩展
extension RemoteNotificationExtension on RemoteNotification {
  Map<String, dynamic> toJson() {
    return {
      'title': title,
      'body': body,
      'android': android?.toJson(),
      'apple': apple?.toJson(),
      'web': web?.toJson(),
    };
  }

  static RemoteNotification fromJson(Map<String, dynamic> json) {
    return RemoteNotification(
      title: json['title'],
      body: json['body'],
      android: json['android'] != null 
          ? AndroidNotificationExtension.fromJson(json['android']) 
          : null,
      apple: json['apple'] != null 
          ? AppleNotificationExtension.fromJson(json['apple']) 
          : null,
      web: json['web'] != null 
          ? WebNotificationExtension.fromJson(json['web']) 
          : null,
    );
  }
}

// Android平台通知字段序列化(不需要平台专属字段可删除)
extension AndroidNotificationExtension on AndroidNotification {
  Map<String, dynamic> toJson() {
    return {
      'channelId': channelId,
      'clickAction': clickAction,
      'color': color,
      'count': count,
      'imageUrl': imageUrl,
      'link': link,
      'priority': priority,
      'smallIcon': smallIcon,
      'sound': sound,
      'ticker': ticker,
      'visibility': visibility,
      'tag': tag,
    };
  }

  static AndroidNotification fromJson(Map<String, dynamic> json) {
    return AndroidNotification(
      channelId: json['channelId'],
      clickAction: json['clickAction'],
      color: json['color'],
      count: json['count'],
      imageUrl: json['imageUrl'],
      link: json['link'],
      priority: json['priority'] ?? AndroidNotificationPriority.defaultPriority,
      smallIcon: json['smallIcon'],
      sound: json['sound'],
      ticker: json['ticker'],
      visibility: json['visibility'] ?? AndroidNotificationVisibility.private,
      tag: json['tag'],
    );
  }
}

// Apple平台通知字段序列化(不需要平台专属字段可删除)
extension AppleNotificationExtension on AppleNotification {
  Map<String, dynamic> toJson() {
    return {
      'badge': badge,
      'sound': sound?.toJson(),
      'imageUrl': imageUrl,
      'subtitle': subtitle,
      'subtitleLocArgs': subtitleLocArgs,
      'subtitleLocKey': subtitleLocKey,
      'threadIdentifier': threadIdentifier,
    };
  }

  static AppleNotification fromJson(Map<String, dynamic> json) {
    return AppleNotification(
      badge: json['badge'],
      sound: json['sound'] != null 
          ? AppleNotificationSoundExtension.fromJson(json['sound']) 
          : null,
      imageUrl: json['imageUrl'],
      subtitle: json['subtitle'],
      subtitleLocArgs: List<String>.from(json['subtitleLocArgs'] ?? []),
      subtitleLocKey: json['subtitleLocKey'],
      threadIdentifier: json['threadIdentifier'],
    );
  }
}

extension AppleNotificationSoundExtension on AppleNotificationSound {
  Map<String, dynamic> toJson() {
    return {
      'critical': critical,
      'name': name,
      'volume': volume,
    };
  }

  static AppleNotificationSound fromJson(Map<String, dynamic> json) {
    return AppleNotificationSound(
      critical: json['critical'] ?? false,
      name: json['name'],
      volume: json['volume'] ?? 1,
    );
  }
}

// Web平台通知字段序列化(不需要平台专属字段可删除)
extension WebNotificationExtension on WebNotification {
  Map<String, dynamic> toJson() {
    return {
      'analyticsLabel': analyticsLabel,
      'image': image,
      'link': link,
    };
  }

  static WebNotification fromJson(Map<String, dynamic> json) {
    return WebNotification(
      analyticsLabel: json['analyticsLabel'],
      image: json['image'],
      link: json['link'],
    );
  }
}

使用示例

存储消息:

saveMessage(RemoteMessage message) async {
  const String tag = "latest_firebase_message";
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  await prefs.setString(tag, jsonEncode(message.toJson())); 
}

读取消息:

Future<RemoteMessage?> getSavedMessage() async {
  const String tag = "latest_firebase_message";
  final SharedPreferences prefs = await SharedPreferences.getInstance();
  final String? messageStr = prefs.getString(tag);
  if (messageStr == null) return null;
  return RemoteMessageJsonExtension.fromJson(jsonDecode(messageStr));
}

如果你不需要保留各平台专属的通知细节字段,可以删掉对应平台的扩展代码,只在RemoteNotification的toJson方法里保留title、body两个核心展示字段即可,代码量会大幅精简。

内容的提问来源于stack exchange,提问作者Sajjad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.20 16:15:48