如何用Flutter的flutter_local_notifications实现复杂重复通知调度?
问题描述
我正在使用Flutter配合flutter_local_notifications插件进行通知调度,目前已能实现单日或每周单次通知,相关代码如下:
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:rxdart/subjects.dart'; import 'package:myapp/models/task.dart'; import 'package:timezone/data/latest_all.dart' as tz; import 'package:timezone/timezone.dart' as tz; class ReceivedNotification { ReceivedNotification({ required this.id, required this.title, required this.body, required this.payload, }); final int id; final String? title; final String? body; final String? payload; } class NotificationPublisher { final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); /// Streams are created so that app can respond to notification-related events /// since the plugin is initialised in the `main` function final BehaviorSubject<ReceivedNotification> didReceiveLocalNotificationSubject = BehaviorSubject<ReceivedNotification>(); final BehaviorSubject<String?> selectNotificationSubject = BehaviorSubject<String?>(); final MethodChannel platform = const MethodChannel('dexterx.dev/myapp'); late final NotificationAppLaunchDetails? notificationAppLaunchDetails; bool get didNotificationLaunchApp => notificationAppLaunchDetails?.didNotificationLaunchApp ?? false; String? selectedNotificationPayload; Future<void> init() async { await _configureLocalTimeZone(); notificationAppLaunchDetails = await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); if (notificationAppLaunchDetails!.didNotificationLaunchApp) { selectedNotificationPayload = notificationAppLaunchDetails!.notificationResponse?.payload; } const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); InitializationSettings initializationSettings = const InitializationSettings( android: initializationSettingsAndroid, ); await flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) { switch (notificationResponse.notificationResponseType) { case NotificationResponseType.selectedNotification: selectNotificationSubject.add(notificationResponse.payload); selectedNotificationPayload = notificationResponse.payload; break; case NotificationResponseType.selectedNotificationAction: // if (notificationResponse.actionId == navigationActionId) { selectNotificationSubject.add(notificationResponse.payload); selectedNotificationPayload = notificationResponse.payload; // } break; } },); _notificationsEnabled = await _isAndroidPermissionGranted(); _notificationsEnabled = await _requestPermissions(); // _configureDidReceiveLocalNotificationSubject(); // _configureSelectNotificationSubject(); } Future<void> _configureLocalTimeZone() async { tz.initializeTimeZones(); final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone(); tz.setLocalLocation(tz.getLocation(timeZoneName)); } Future<bool> _isAndroidPermissionGranted() async { if (Platform.isAndroid) { final bool? granted = await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.areNotificationsEnabled(); return granted ?? false; } return false; } Future<bool> _requestPermissions() async { if (Platform.isAndroid) { final bool? granted = await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.requestPermission(); return granted ?? false; } return false; } void dispose() { didReceiveLocalNotificationSubject.close(); selectNotificationSubject.close(); } // NOTIFY tz.TZDateTime _scheduleTimeInstance(DateTime date, TimeOfDay time) { return tz.TZDateTime( tz.local, date.year, date.month, date.day, time.hour, time.minute, ); } Future<void> zonedScheduleNotification(int notificationId, Task task) async { print(notificationId); var scheduleDT = _scheduleTimeInstance(task.scheduleDate!, task.scheduleTime!); await flutterLocalNotificationsPlugin.zonedSchedule( notificationId, task.title, task.details, scheduleDT, const NotificationDetails( android: AndroidNotificationDetails( 'myapp-id', 'myapp', channelDescription: 'notification channel for myapp', importance: Importance.max, priority: Priority.high, ticker: 'ticker') ), androidAllowWhileIdle: true, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime); } Future<void> cancelNotification(notificationId) async { await flutterLocalNotificationsPlugin.cancel(notificationId); } }
但我需要实现更复杂的调度需求:每周二和周五的8:00至20:00,每小时触发一次通知。请问是否必须创建12×2个独立的通知?能否通过单个API调用完成该需求?
解决方案
首先明确:flutter_local_notifications目前不支持通过单个API调用直接实现这种"多重复周期+时间范围"的复杂调度,因为原生Android/iOS的通知调度API本身就没有这么灵活的规则配置。不过你不需要手动逐个创建24个通知,可以通过以下更高效的方式实现:
最优方案:批量创建带每周重复规则的通知
你只需要为周二8点、周二9点……周二20点,周五8点……周五20点,每个时间点创建一个带每周重复规则的通知,总共24个,但可以通过循环批量生成,代码量不会增加太多,维护成本也低。
具体实现代码
在你的NotificationPublisher类中添加以下方法:
Future<void> scheduleWeeklyRangeNotifications(int baseId, Task task) async { // 目标星期:周二(2)、周五(5),注意tz.TZDateTime.weekday中周一为1,周日为7 final targetWeekdays = [2, 5]; // 时间范围:8点到20点(包含两端) final startHour = 8; final endHour = 20; for (final weekday in targetWeekdays) { for (int hour = startHour; hour <= endHour; hour++) { // 计算每个时间点的初始触发时间(最近的对应星期的该小时) final now = tz.TZDateTime.now(tz.local); tz.TZDateTime scheduledTime = tz.TZDateTime( tz.local, now.year, now.month, now.day, hour, 0, ); // 如果当前时间已经过了本周的该时间点,就安排到下周 if (scheduledTime.isBefore(now) || scheduledTime.weekday != weekday) { final daysToAdd = (weekday - scheduledTime.weekday + 7) % 7; scheduledTime = scheduledTime.add(Duration(days: daysToAdd)); if (scheduledTime.isBefore(now)) { scheduledTime = scheduledTime.add(const Duration(days: 7)); } } // 使用baseId + 偏移量作为每个通知的唯一ID,避免冲突 final notificationId = baseId + (weekday - 2) * 13 + (hour - startHour); await flutterLocalNotificationsPlugin.zonedSchedule( notificationId, task.title, task.details, scheduledTime, const NotificationDetails( android: AndroidNotificationDetails( 'myapp-id', 'myapp', channelDescription: 'notification channel for myapp', importance: Importance.max, priority: Priority.high, ticker: 'ticker', ), ), androidAllowWhileIdle: true, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, // 设置每周同一时间重复触发 matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime, ); } } }
代码说明
matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime:这个参数让通知每周固定星期和时间自动重复,无需手动设置重复间隔。- 通过循环自动生成唯一的通知ID,既避免冲突,也方便后续批量取消。
- 自动计算最近的有效触发时间,确保不会错过本周的时间点。
批量取消这组通知
如果需要取消这一系列通知,可以添加以下方法:
Future<void> cancelWeeklyRangeNotifications(int baseId) async { final targetWeekdays = [2, 5]; final startHour = 8; final endHour = 20; for (final weekday in targetWeekdays) { for (int hour = startHour; hour <= endHour; hour++) { final notificationId = baseId + (weekday - 2) * 13 + (hour - startHour); await flutterLocalNotificationsPlugin.cancel(notificationId); } } }
总结
虽然不能通过单个API调用完成,但通过循环批量创建带每周重复规则的通知,代码实现简洁,维护成本低,是目前flutter_local_notifications实现这类复杂调度的标准做法。
内容的提问来源于stack exchange,提问作者Randomize
相关产品推荐
相关产品推荐

