Skip to content

Refactor and redesign repeating notifications #2492

Open
@Levi-Lesches

Description

@Levi-Lesches

I'm writing up the usage guide now (see #2477), and quickly found that scheduling repeating notifications is complicated. I'm sure this is at least partially historical, but users have three methods to choose from:

  • zonedSchedule(), with a non-null matchDateTimeComponents
  • periodicallyShow(), with a given RepeatInterval
  • periodicallyShowWithDuration(), with a given Duration

Fundamentally, there are four ways to show a scheduled notification:

  • show once
  • show then repeat given certain conditions, like date, day of week, etc
  • show then repeat on a given interval

The differences between the methods

From what I can tell, the main difference between zonedSchedule() and periodicallyShow() is that periodicallyShow() starts now, whereas zonedSchedule() takes a TZDateTime parameter. It's true that UNTimeIntervalNotificationTrigger doesn't take in a date/time to start the timer and starts immediately. So there's no way to say "send a notification every 30 minutes, starting in 2 hours from now".

To get around this, we can use an approach similar to Android, where we schedule a background task for the given date and time, which then schedules the first UNTimeIntervalNotificationTrigger.

The only difference between periodicallyShow() and periodicallyShowWithDuration() is the ability to pass an arbitrary duration, but Android, iOS, and MacOS support an arbitrary duration, so we should just always allow an arbitrary duration, preferring Duration(days: 1) to RepeatInterval.daily. In fact, on Android and Darwin, the enum is converted to milliseconds, and on Linux and Windows periodic notifications are not supported at all.

New proposal

sealed class NotificationRepeatDetails { }

class NotificationRepeatInterval extends NotificationRepeatDetails { 
  Duration duration;
}

class NotificationRepeatDate extends NotificationRepeatDetails { 
  DateTimeComponents components;
}

void schedule({
  required int id,
  required TZDateTime scheduleDateTime,
  String? title,
  String? body,
  String? payload,
  NotificationDetails? details, 
  NotificationRepeatDetails? repeatsOn,
);

Since this will be a breaking change anyway, I renamed some parameters. I removed uiLocalNotificationDateInterpretation since it only applies to devices running iOS 10 or earlier, but Flutter hasn't supported those versions since Flutter 3.13, and this plugin only supports Flutter ^3.19. I also think we should move androidScheduleMode to be an optional parameter on AndroidNotificationDetails(), defaulting to something reasonable like .inexact, but I can see an argument not to since it doesn't apply to show().

Examples

final location = getLocation(...);
final now = TZDateTime.now(location);

// Schedule a notification tomorrow at 8 pm
plugin.schedule(
  id: 0,
  scheduledDateTime: now.copyWith(hour: 20, minute: 00),
  // title, body, details
);

// Schedule a repeating reminder, every day at noon
plugin.schedule(
  id: 1, 
  scheduledDateTime: now.copyWith(hour: 12, minute: 00),
  repeatsOn: NotificationRepeatDate(DateTimeComponents.time),
  // title, body, details
);

// Schedule a reminder every 30 minutes to take a break, starting in 30 minutes from now
plugin.schedule(
  id: 2, 
  scheduledDateTime: now.add(Duration(minutes: 30)),
  repeatsOn: NotificationRepeatInterval(Duration(minutes: 30)),
  // title, body, details
);

@MaikuB What are your thoughts on implementing this for v20? It is breaking but it would significantly simplify the API, favoring one method over three, and rid us of a lot of unused code. Again, I'd be happy to contribute a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions