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

Firebase通知热重载后重复接收问题求助

解决Firebase通知热重载后重复弹窗问题

问题根源

每次热重载时,_handlePushNotifications函数会被重新执行,导致FirebaseMessaging.onMessage.listen被多次注册。Flutter热重载不会自动清除之前注册的监听器,因此每重载一次就会新增一个监听回调。收到通知时,所有已注册的回调都会触发,从而弹出多个重复弹窗。

解决方案

通过管理StreamSubscription对象,确保每次注册新监听器前先取消旧的订阅,避免重复监听。同时优化弹窗关闭逻辑,移除冗余代码。

修改后代码示例

// 全局/类成员变量:保存通知监听的订阅对象,用于后续取消订阅
StreamSubscription<RemoteMessage>? _messageSubscription;
// 全局/类成员变量:弹窗全局Key,避免每次函数调用重新创建
final GlobalKey<State<StatefulWidget>> dialogKey = GlobalKey<State>();

void _handlePushNotifications(NotificationViewModel notificationViewModel) {
    debugPrint('handlePushN activated'); //test
    FirebaseMessaging messaging = FirebaseMessaging.instance;
    messaging.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );

    messaging.getToken().then((token) async {
      debugPrint('Device Token: $token');
      var storedToken = await TokenHandler.loadTokenNoException("device_token");
      if (token != null && token != storedToken) {
        notificationViewModel.postDeviceToken(token);
      }
    });

    messaging.onTokenRefresh.listen((token) async {
      var storedToken = await TokenHandler.loadTokenNoException("device_token");
      if (token != storedToken) {
        notificationViewModel.postDeviceToken(token);
      }
      debugPrint('Token refreshed: $token');
    });

    // 取消之前的订阅,避免重复监听
    _messageSubscription?.cancel();
    // 重新注册通知监听
    _messageSubscription = FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      debugPrint("New message arrived: ${message.data.entries.first}");
      String? msg = message.data['msg'];
      String? sum = message.data['sum'];

      if (msg != null && sum != null) {
        // 关闭已存在的弹窗
        if (dialogKey.currentState != null && dialogKey.currentState!.mounted) {
          Navigator.of(dialogKey.currentContext!).pop();
        }

        showDialog(
          context: navigatorKey.currentContext!,
          barrierDismissible: false,
          builder: (BuildContext context) {
            return AlertDialog(
              key: dialogKey,
              title: Text(sum),
              content: SingleChildScrollView(
                child: ListBody(
                  children: <Widget>[
                    Text(msg),
                  ],
                ),
              ),
              actions: [
                TextButton(
                  onPressed: () {
                    // 只需一行即可关闭弹窗,移除冗余的pop调用
                    Navigator.of(context).pop();
                  },
                  child: const Text('Dismiss'), 
                ),
              ],
            );
          },
        );
      }
    });
}

核心修改点

  1. 管理订阅生命周期:新增StreamSubscription变量保存监听实例,每次执行函数时先取消旧订阅,再注册新订阅,彻底避免多监听器共存。
  2. 固定弹窗全局Key:将dialogKey提升为全局/类成员变量,避免每次函数调用重新创建,保证弹窗状态的一致性。
  3. 简化弹窗关闭逻辑:移除重复的Navigator.pop调用,利用弹窗上下文直接关闭即可。

额外优化建议

如果该逻辑是在StatefulWidget中实现,建议将监听注册放在initState,取消订阅放在dispose中,利用Flutter的生命周期自动管理:

class _YourWidgetState extends State<YourWidget> {
  StreamSubscription<RemoteMessage>? _messageSubscription;
  final GlobalKey<State<StatefulWidget>> dialogKey = GlobalKey<State>();

  @override
  void initState() {
    super.initState();
    _handlePushNotifications(yourNotificationViewModel);
  }

  @override
  void dispose() {
    _messageSubscription?.cancel();
    super.dispose();
  }

  // _handlePushNotifications函数逻辑同上
}

内容的提问来源于stack exchange,提问作者Milena Rode-Kotzé

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 22:15:03