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
相关产品推荐
相关产品推荐

