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

Flutter通知添加倒计时器:点击取消按钮无法关闭Stream问题求助

解决Flutter推送通知倒计时点击Cancel无法停止Stream的问题

核心问题分析

点击通知的Cancel按钮时,当前代码没有将动作回调与对应的StreamController绑定,导致无法找到并关闭目标Stream、停止Timer。需要通过关联通知ID与StreamController,并监听通知动作回调来实现精准控制。

解决方案步骤及代码修改

1. 存储通知对应的StreamController

在类中定义一个Map,用于保存每个通知ID关联的StreamController:

final Map<int, StreamController<String>> _notificationControllers = {};

2. 修改通知创建逻辑,绑定控制器与通知ID

更新notificationWithTimer方法,将StreamController存入Map,并处理重复通知的清理:

void notificationWithTimer(DateTime time, RemoteMessage message) {
  final notificationId = message.notification.hashCode;
  
  // 先清理同ID的旧通知与控制器
  if (_notificationControllers.containsKey(notificationId)) {
    _notificationControllers[notificationId]?.close();
    _notificationControllers.remove(notificationId);
    flutterLocalNotificationsPlugin.cancel(notificationId);
  }

  final streamController = countdownTimer.notificationTimer(time);
  _notificationControllers[notificationId] = streamController;

  streamController.stream.listen((event) async {
    await flutterLocalNotificationsPlugin.show(
      notificationId,
      '${message.notification?.title}',
      '${message.notification?.body}... ${event.toString()}',
      await notificationDetails(
        image: '${message.notification?.android?.imageUrl}',
        actions: const [
          AndroidNotificationAction(
            'cancel',
            'Cancel',
            showsUserInterface: true, // 确保触发动作回调
            cancelNotification: true, // 点击后自动取消通知
          ),
        ],
      ),
      payload: jsonEncode(message.data),
    );
  }).onDone(() {
    flutterLocalNotificationsPlugin.cancel(notificationId);
    _notificationControllers.remove(notificationId);
  });
}

3. 监听通知动作回调,处理Cancel事件

初始化flutterLocalNotificationsPlugin时添加动作回调,捕获Cancel操作并关闭对应Stream:

void initNotifications() async {
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('@mipmap/ic_launcher');
  const InitializationSettings initializationSettings =
      InitializationSettings(android: initializationSettingsAndroid);
  
  await flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (NotificationResponse response) async {
      if (response.actionId == 'cancel' && response.id != null) {
        final notificationId = response.id!;
        // 关闭对应Stream并清理资源
        if (_notificationControllers.containsKey(notificationId)) {
          _notificationControllers[notificationId]?.close();
          _notificationControllers.remove(notificationId);
        }
      }
    },
  );
}

4. 优化倒计时Stream实现,确保资源释放

修正notificationTimer方法,完善Timer停止与Stream关闭逻辑:

StreamController<String> notificationTimer(DateTime endTime) {
  final timeStreamController = StreamController<String>();
  Timer? timer;
  Duration difference = endTime.isBefore(DateTime.now()) 
      ? Duration.zero 
      : endTime.difference(DateTime.now());

  String _formatTwoDigits(int n) => n.toString().padLeft(2, '0');

  void tick(_) {
    difference = endTime.difference(DateTime.now());
    if (difference <= Duration.zero) {
      timer?.cancel();
      if (!timeStreamController.isClosed) {
        timeStreamController.close();
      }
      return;
    }
    final hours = _formatTwoDigits(difference.inHours.remainder(24));
    final minutes = _formatTwoDigits(difference.inMinutes.remainder(60));
    final seconds = _formatTwoDigits(difference.inSeconds.remainder(60));
    final res = "$hours:$minutes:$seconds";
    if (!timeStreamController.isClosed) {
      timeStreamController.sink.add(res);
    }
  }

  void stopTimer() {
    timer?.cancel();
    timer = null;
  }

  void startTimer() {
    if (difference != Duration.zero) {
      timer = Timer.periodic(const Duration(seconds: 1), tick);
    } else {
      timeStreamController.close();
    }
  }

  timeStreamController.onListen = startTimer;
  timeStreamController.onPause = stopTimer;
  timeStreamController.onCancel = () {
    stopTimer();
    if (!timeStreamController.isClosed) {
      timeStreamController.close();
    }
  };

  // 初始发送一次倒计时状态
  if (difference > Duration.zero) {
    final hours = _formatTwoDigits(difference.inHours.remainder(24));
    final minutes = _formatTwoDigits(difference.inMinutes.remainder(60));
    final seconds = _formatTwoDigits(difference.inSeconds.remainder(60));
    timeStreamController.sink.add("$hours:$minutes:$seconds");
  }

  return timeStreamController;
}

关键修改说明

  • 通过Map关联通知ID与StreamController,实现点击Cancel时精准定位目标资源。
  • 给AndroidNotificationAction添加showsUserInterface: true,确保动作能触发回调。
  • 在StreamController的onCancel回调中强制停止Timer并关闭Stream,避免资源泄漏。
  • 处理重复通知场景,先清理旧资源再创建新通知,防止多个Stream同时运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 16:55:41