Scheduled and Periodic Local Notifications
Scheduled and Periodic Local Notifications
Triggering a notification immediately is straightforward, but real-world apps often need to schedule a notification to fire at a precise future date and time, or set up a repeating notification that fires every day, every week, or on a custom interval. The flutter_local_notifications package exposes a dedicated zoned scheduling API that handles time zones correctly, avoiding the classic daylight-saving and UTC-offset bugs.
timezone package alongside flutter_local_notifications. Always call tz.initializeTimeZones() at app start and set the local time zone with tz.setLocalLocation() before scheduling any notification.Setting Up the timezone Package
Add both packages to pubspec.yaml, then initialise the time-zone database in your main() function before runApp():
Initialising the Time-Zone Database
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Load the full time-zone database
tz.initializeTimeZones();
// 2. Set the device's local time zone (reads from the OS)
final String currentTimeZone =
await FlutterTimezone.getLocalTimezone(); // flutter_timezone package
tz.setLocalLocation(tz.getLocation(currentTimeZone));
// 3. Initialise flutter_local_notifications (Android + iOS)
const AndroidInitializationSettings androidSettings =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings iosSettings =
DarwinInitializationSettings();
const InitializationSettings initSettings = InitializationSettings(
android: androidSettings,
iOS: iosSettings,
);
await flutterLocalNotificationsPlugin.initialize(initSettings);
runApp(const MyApp());
}
Scheduling a One-Time Notification
Use zonedSchedule() to fire a notification at an exact date and time in the user's local time zone. The method takes a TZDateTime — a time-zone-aware DateTime — so the OS fires it at the right wall-clock moment regardless of UTC offset.
TZDateTime from tz.local rather than tz.UTC so that daylight-saving transitions are handled transparently.Scheduling a One-Time Notification
import 'package:timezone/timezone.dart' as tz;
Future<void> scheduleOneTimeNotification({
required int id,
required String title,
required String body,
required DateTime scheduledDate,
}) async {
final tz.TZDateTime tzScheduled =
tz.TZDateTime.from(scheduledDate, tz.local);
const AndroidNotificationDetails androidDetails =
AndroidNotificationDetails(
'scheduled_channel',
'Scheduled Notifications',
channelDescription: 'One-time scheduled reminders',
importance: Importance.high,
priority: Priority.high,
);
const NotificationDetails notifDetails =
NotificationDetails(android: androidDetails);
await flutterLocalNotificationsPlugin.zonedSchedule(
id,
title,
body,
tzScheduled,
notifDetails,
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
);
}
// Usage: fire a reminder 10 minutes from now
void scheduleReminder() {
final DateTime target =
DateTime.now().add(const Duration(minutes: 10));
scheduleOneTimeNotification(
id: 42,
title: 'Meeting Reminder',
body: 'Your stand-up starts in 10 minutes.',
scheduledDate: target,
);
}
Setting Up Repeating Notifications
For notifications that recur on a fixed cadence, use periodicallyShow() (simple intervals) or zonedSchedule() with a DateTimeComponents match parameter (calendar-aligned repeats). The two approaches serve different needs:
periodicallyShow()— fires every N seconds/minutes/hours/days measured from the call time. Simple but not calendar-aware.zonedSchedule()+matchDateTimeComponents— fires at a specific time-of-day (daily) or specific day+time (weekly). Calendar-aware and respects DST.
Daily and Weekly Repeating Notifications
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;
/// Fires every day at [hour]:[minute] in the device's local time zone.
Future<void> scheduleDailyNotification({
required int id,
required String title,
required String body,
required int hour,
required int minute,
}) async {
final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
tz.TZDateTime scheduledDate = tz.TZDateTime(
tz.local,
now.year,
now.month,
now.day,
hour,
minute,
);
// If the time has already passed today, start tomorrow
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
const AndroidNotificationDetails androidDetails =
AndroidNotificationDetails(
'daily_channel',
'Daily Notifications',
channelDescription: 'Recurring daily reminders',
importance: Importance.defaultImportance,
);
const NotificationDetails notifDetails =
NotificationDetails(android: androidDetails);
await flutterLocalNotificationsPlugin.zonedSchedule(
id,
title,
body,
scheduledDate,
notifDetails,
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
// KEY: repeat at the same time-of-day every day
matchDateTimeComponents: DateTimeComponents.time,
);
}
/// Fires every week on [weekday] (DateTime.monday … .sunday) at [hour]:[minute].
Future<void> scheduleWeeklyNotification({
required int id,
required String title,
required String body,
required int weekday,
required int hour,
required int minute,
}) async {
final tz.TZDateTime firstOccurrence = _nextInstanceOfWeekdayTime(
weekday: weekday,
hour: hour,
minute: minute,
);
const AndroidNotificationDetails androidDetails =
AndroidNotificationDetails(
'weekly_channel',
'Weekly Notifications',
channelDescription: 'Recurring weekly reminders',
);
const NotificationDetails notifDetails =
NotificationDetails(android: androidDetails);
await flutterLocalNotificationsPlugin.zonedSchedule(
id,
title,
body,
firstOccurrence,
notifDetails,
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
// KEY: repeat at the same weekday + time every week
matchDateTimeComponents: DateTimeComponents.dayOfWeekAndTime,
);
}
tz.TZDateTime _nextInstanceOfWeekdayTime({
required int weekday,
required int hour,
required int minute,
}) {
final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
tz.TZDateTime candidate = tz.TZDateTime(
tz.local,
now.year,
now.month,
now.day,
hour,
minute,
);
while (candidate.weekday != weekday || candidate.isBefore(now)) {
candidate = candidate.add(const Duration(days: 1));
}
return candidate;
}
Cancelling Scheduled Notifications
Every scheduled notification is identified by the integer id you provide. Use that id to cancel a specific notification, or call cancelAll() to remove every pending notification in one call.
flutterLocalNotificationsPlugin.cancel(id)— cancels the notification with the given id.flutterLocalNotificationsPlugin.cancelAll()— cancels all pending notifications.flutterLocalNotificationsPlugin.pendingNotificationRequests()— returns a list of all currently queued notifications so you can inspect or cancel them selectively.
SCHEDULE_EXACT_ALARM or USE_EXACT_ALARM permission in AndroidManifest.xml. Without it, AndroidScheduleMode.exactAllowWhileIdle will be silently downgraded to an inexact alarm. Always declare this permission and, for API 31, prompt the user to grant it via canScheduleExactNotifications().AndroidScheduleMode Options
Understanding the available schedule modes helps you pick the right balance between precision and battery impact:
exact— fires at the exact time but may be deferred if the device is in Doze mode.exactAllowWhileIdle— fires even during Doze, the most reliable option for user-facing reminders.inexact— OS batches the alarm for efficiency; delivery can vary by minutes. Best for non-critical background tasks.
Summary
The zoned scheduling API in flutter_local_notifications provides precise, time-zone-correct notification delivery. Use zonedSchedule() with a TZDateTime for one-time reminders, add matchDateTimeComponents: DateTimeComponents.time for daily repeats, or DateTimeComponents.dayOfWeekAndTime for weekly repeats. Always initialise the timezone package at startup, declare the exact-alarm permission on Android 12+, and store notification IDs so you can cancel them cleanly.