void selectNotification(String payload) async {
if (payload != null) {
debugPrint('notification payload: $payload');
}
await Navigator.push(
context,
MaterialPageRoute<void>(builder: (context) => SecondScreen(payload)),
);
}
In the real world, this payload could represent the id of the item you want to display the details of. Once the initialisation is complete, then you can manage the displaying of notifications.
The IOSInitializationSettings and MacOSInitializationSettings provides default settings on how the notification be presented when it is triggered and the application is in the foreground. There are optional named parameters that can be modified to suit your application's purposes. Here, it is omitted and the default values for these named properties is set such that all presentation options (alert, sound, badge) are enabled.
On iOS and macOS, initialisation may show a prompt to requires users to give the application permission to display notifications (note: permissions don't need to be requested on Android). Depending on when this happens, this may not be the ideal user experience for your application. If so, please refer to the next section on how to work around this.
For an explanation of the onDidReceiveLocalNotification callback associated with the IOSInitializationSettings class, please read this.
[iOS (all supported versions) and macOS 10.14+] Requesting notification permissions
The constructor for the IOSInitializationSettings and MacOSInitializationSettings classes has three named parameters (requestSoundPermission, requestBadgePermission and requestAlertPermission) that controls which permissions are being requested. If you want to request permissions at a later point in your application on iOS, set all of the above to false when initialising the plugin.
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('app_icon');
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
onDidReceiveLocalNotification: onDidReceiveLocalNotification,
);
final MacOSInitializationSettings initializationSettingsMacOS =
MacOSInitializationSettings(
requestAlertPermission: false,
requestBadgePermission: false,
requestSoundPermission: false);
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
macOS: initializationSettingsMacOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
Then call the requestPermissions method with desired permissions at the appropriate point in your application
For iOS:
final bool result = await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
For macOS:
final bool result = await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
MacOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
Here the call to flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>() returns the iOS implementation of the plugin that contains APIs specific to iOS if the application is running on iOS. Similarly, the macOS implementation is returned by calling flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>(). The ?. operator is used as the result will be null when run on other platforms. Developers may alternatively choose to guard this call by checking the platform their application is running on.
Displaying a notification
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails('your channel id', 'your channel name',
channelDescription: 'your channel description',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker');
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0, 'plain title', 'plain body', platformChannelSpecifics,
payload: 'item x');
Here, the first argument is the id of notification and is common to all methods that would result in a notification being shown. This is typically set a unique value per notification as using the same id multiple times would result in a notification being updated/overwritten.
The details specific to the Android platform are also specified. This includes the channel details that is required for Android 8.0+. Whilst not shown, it's possible to specify details for iOS and macOS as well using the optional iOS and macOS named parameters if needed. The payload has been specified ('item x'), that will passed back through your application when the user has tapped on a notification. Note that for Android devices that notifications will only in appear in the tray and won't appear as a toast aka heads-up notification unless things like the priority/importance has been set appropriately. Refer to the Android docs (https://developer.android.com/guide/topics/ui/notifiers/notifications.html#Heads-up) for additional information. The "ticker" text is passed here is optional and specific to Android. This allows for text to be shown in the status bar on older versions of Android when the notification is shown.
Scheduling a notification
Starting in version 2.0 of the plugin, scheduling notifications now requires developers to specify a date and time relative to a specific time zone. This is to solve issues with daylight saving time that existed in the schedule method that is now deprecated. A new zonedSchedule method is provided that expects an instance TZDateTime class provided by the timezone package. As the flutter_local_notificationsplugin already depends on the timezone package, it's not necessary for developers to add the timezonepackage as a direct dependency. In other words, the timezone package will be a transitive dependency after you add the flutter_local_notifications plugin as a dependency in your application.
Usage of the timezone package requires initialisation that is covered in the package's readme. For convenience the following are code snippets used by the example app.
Import the timezone package
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
Initialise the time zone database
tz.initializeTimeZones();
Once the time zone database has been initialised, developers may optionally want to set a default local location/time zone
tz.setLocalLocation(tz.getLocation(timeZoneName));
The timezone package doesn't provide a way to obtain the current time zone on the device so developers will need to use platform channels or use other packages that may be able to provide the information. The example app uses the flutter_native_timezone plugin.
Assuming the local location has been set, the zonedSchedule method can then be called in a manner similar to the following code
await flutterLocalNotificationsPlugin.zonedSchedule(
0,
'scheduled title',
'scheduled body',
tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
const NotificationDetails(
android: AndroidNotificationDetails(
'your channel id', 'your channel name',
channelDescription: 'your channel description')),
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime);
On Android, the androidAllowWhileIdle is used to determine if the notification should be delivered at the specified time even when the device in a low-power idle mode.
The uiLocalNotificationDateInterpretation is required as on iOS versions older than 10 as time zone support is limited. This means it's not possible schedule a notification for another time zone and have iOS adjust the time the notification will appear when daylight saving time happens. With this parameter, it is used to determine if the scheduled date should be interpreted as absolute time or wall clock time.
There is an optional matchDateTimeComponents parameter that can be used to schedule a notification to appear on a daily or weekly basis by telling the plugin to match on the time or a combination of day of the week and time respectively.
If you are trying to update your code so it doesn't use the deprecated methods for showing daily or weekly notifications that occur on a specific day of the week then you'll need to perform calculations that would determine the next instance of a date that meets the conditions for your application. See the example application that shows one of the ways that can be done e.g. how schedule a weekly notification to occur on Monday 10:00AM.
Periodically show a notification with a specified interval
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(
'repeating channel id', 'repeating channel name',
channelDescription: 'repeating description');
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.periodicallyShow(0, 'repeating title',
'repeating body', RepeatInterval.everyMinute, platformChannelSpecifics,
androidAllowWhileIdle: true);
Retrieving pending notification requests
final List<PendingNotificationRequest> pendingNotificationRequests =
await flutterLocalNotificationsPlugin.pendingNotificationRequests();
[Android only] Retrieving active notifications
final List<ActiveNotification> activeNotifications =
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.getActiveNotifications();
Grouping notifications
iOS
For iOS, you can specify threadIdentifier in IOSNotificationDetails. Notifications with the same threadIdentifier will get grouped together automatically.
const IOSNotificationDetails iOSPlatformChannelSpecifics =
IOSNotificationDetails(threadIdentifier: 'thread_id');
Android
This is a "translation" of the sample available at https://developer.android.com/training/notify-user/group.html
const String groupKey = 'com.android.example.WORK_EMAIL';
const String groupChannelId = 'grouped channel id';
const String groupChannelName = 'grouped channel name';
const String groupChannelDescription = 'grouped channel description';
// example based on https://developer.android.com/training/notify-user/group.html
const AndroidNotificationDetails firstNotificationAndroidSpecifics =
AndroidNotificationDetails(groupChannelId, groupChannelName,
channelDescription: groupChannelDescription,
importance: Importance.max,
priority: Priority.high,
groupKey: groupKey);
const NotificationDetails firstNotificationPlatformSpecifics =
NotificationDetails(android: firstNotificationAndroidSpecifics);
await flutterLocalNotificationsPlugin.show(1, 'Alex Faarborg',
'You will not believe...', firstNotificationPlatformSpecifics);
const AndroidNotificationDetails secondNotificationAndroidSpecifics =
AndroidNotificationDetails(groupChannelId, groupChannelName,
channelDescription: groupChannelDescription,
importance: Importance.max,
priority: Priority.high,
groupKey: groupKey);
const NotificationDetails secondNotificationPlatformSpecifics =
NotificationDetails(android: secondNotificationAndroidSpecifics);
await flutterLocalNotificationsPlugin.show(
2,
'Jeff Chang',
'Please join us to celebrate the...',
secondNotificationPlatformSpecifics);
// Create the summary notification to support older devices that pre-date
/// Android 7.0 (API level 24).
///
/// Recommended to create this regardless as the behaviour may vary as
/// mentioned in https://developer.android.com/training/notify-user/group
const List lines = [
'Alex Faarborg Check this out',
'Jeff Chang Launch Party'
];
const InboxStyleInformation inboxStyleInformation = InboxStyleInformation(
lines,
contentTitle: '2 messages',
summaryText: 'janedoe@example.com');
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(groupChannelId, groupChannelName,
channelDescription: groupChannelDescription,
styleInformation: inboxStyleInformation,
groupKey: groupKey,
setAsGroupSummary: true);
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
3, 'Attention', 'Two messages', platformChannelSpecifics);
Cancelling/deleting a notification
// cancel the notification with id value of zero
await flutterLocalNotificationsPlugin.cancel(0);
Cancelling/deleting all notifications
await flutterLocalNotificationsPlugin.cancelAll();
Getting details on if the app was launched via a notification created by this plugin
final NotificationAppLaunchDetails notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
[iOS only] Periodic notifications showing up after reinstallation
If you have set notifications to be shown periodically on older iOS versions (< 10) and the application was uninstalled without cancelling all alarms, then the next time it's installed you may see the "old" notifications being fired. If this is not the desired behaviour then you can add code similar to the following to the didFinishLaunchingWithOptions method of your AppDelegate class.
Objective-C:
if(![[NSUserDefaults standardUserDefaults]objectForKey:@"Notification"]){
[[UIApplication sharedApplication] cancelAllLocalNotifications];
[[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"Notification"];
}
Swift:
if(!UserDefaults.standard.bool(forKey: "Notification")) {
UIApplication.shared.cancelAllLocalNotifications()
UserDefaults.standard.set(true, forKey: "Notification")
}
📈 Testing
As the plugin class is not static, it is possible to mock and verify its behaviour when writing tests as part of your application. Check the source code for a sample test suite that has been kindly implemented (test/flutter_local_notifications_test.dart) that demonstrates how this can be done.
If you decide to use the plugin class directly as part of your tests, the methods will be mostly no-op and methods that return data will return default values.
Part of this is because the plugin detects if you're running on a supported plugin to determine which platform implementation of the plugin should be used. If the platform isn't supported, it will default to the aforementioned behaviour to reduce friction when writing tests. If this not desired then consider using mocks.
If a platform-specific implementation of the plugin is required for your tests, use the debugDefaultTargetPlatformOverride property provided by the Flutter framework.