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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 14:35:35