The Complete Guide to Local Notifications in Flutter
Notifications are a powerful tool for driving user engagement and retention in mobile apps. When used effectively, they can boost app open rates by up to 40% and increase 90-day retention by 3-10x. (Source)
As a Flutter developer, you have two main options for adding notifications to your app:
-
Push Notifications – Sent from an external server to a user‘s device, even when the app isn‘t running. Requires additional setup and infrastructure.
-
Local Notifications – Scheduled and displayed directly from within the app, without needing a network connection or backend service. Ideal for reminders, alerts, and time-sensitive content.
In this guide, we‘ll take a deep dive into local notifications in Flutter. You‘ll learn how to use the flutter_local_notifications
package to easily add notifications to your app, customize their appearance and behavior, and handle user interactions. Let‘s get started!
How Local Notifications Work
Under the hood, local notifications are powered by platform-specific APIs and services:
-
On Android, the
NotificationManager
andAlarmManager
classes handle scheduling and displaying notifications. Notifications can be customized with icons, sounds, vibration patterns, and custom layouts. -
On iOS, the
UserNotifications
framework is responsible for notifications. Apps must request permission from the user before sending any notifications. Customization options include titles, subtitles, badges, and category identifiers.
The flutter_local_notifications
package abstracts away these platform differences and provides a unified Dart API for creating, scheduling, and managing notifications across both Android and iOS.
Adding the Dependency
To use the package in your Flutter project, add this line to your pubspec.yaml
file:
dependencies:
flutter_local_notifications: ^6.0.0
Then run flutter pub get
to pull in the latest version. At the time of writing, 6.0.0 is the most recent stable release.
Initializing the Plugin
Before you can display notifications, you need to initialize the plugin and request the necessary permissions. This code typically goes in your main()
function:
import ‘package:flutter_local_notifications/flutter_local_notifications.dart‘;
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Android notification channel setup
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings(‘app_icon‘);
// iOS notification setup
const IOSInitializationSettings initializationSettingsIOS = IOSInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
);
// Combined initialization settings
const InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
);
// Initialize the plugin
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
runApp(MyApp());
}
On Android, you need to define a notification channel with a unique ID, name, and description. The app_icon
parameter specifies the default icon to show in the status bar.
On iOS, you can choose which permissions to request from the user by setting the requestSoundPermission
, requestBadgePermission
, and requestAlertPermission
flags. It‘s recommended to wait until the user takes a relevant action, like tapping a "Enable Notifications" button, before requesting permissions.
Scheduling Notifications
To schedule a notification, you create an instance of the AndroidNotificationDetails
or IOSNotificationDetails
class to configure the notification‘s appearance and behavior. Then you pass those details to the zonedSchedule()
method, along with a unique ID, title, body, and timestamp:
// Android notification details
const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
‘channel ID‘,
‘channel name‘,
‘channel description‘,
importance: Importance.max,
priority: Priority.high,
color: Colors.blue,
enableLights: true,
);
// iOS notification details
const IOSNotificationDetails iosDetails = IOSNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
badgeNumber: 1,
attachments: null,
subtitle: ‘This is a longer subtitle‘,
);
// Combined notification details
const NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidDetails, iOS: iosDetails);
// Schedule for 5 seconds from now
final scheduledTime = tz.TZDateTime.now(tz.local).add(Duration(seconds: 5));
// Schedule the notification
await flutterLocalNotificationsPlugin.zonedSchedule(
0, // Unique ID
‘Scheduled Notification‘,
‘This notification will appear in 5 seconds.‘,
scheduledTime,
platformChannelSpecifics,
uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
androidAllowWhileIdle: true,
payload: ‘NOTIFICATION_PAYLOAD‘,
);
The zonedSchedule()
method lets you specify an exact timestamp for the notification delivery, taking into account the user‘s local time zone. This ensures notifications appear at the intended time regardless of the user‘s location.
The payload
parameter is a string value that you can use to pass custom data to the notification. When the user taps the notification to open your app, you can access this payload to determine what action to take.
Handling Notification Interactions
When a user taps a notification to open your app, you typically want to navigate to a specific screen or perform some action based on the notification‘s contents. To enable this, you need to provide a callback to the onSelectNotification
parameter when initializing the plugin:
void main() async {
// ...
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: onSelectNotification,
);
// ...
}
Future<void> onSelectNotification(String payload) async {
if (payload != null) {
debugPrint(‘Notification payload: $payload‘);
}
// Handle the notification tap action
// For example: Navigate to a specific screen
}
The onSelectNotification
callback receives the payload
string that you specified when scheduling the notification. You can parse this payload and use it to determine what action to take in your app.
For example, you might include a screen name or ID in the payload, and use that to navigate to the corresponding screen in your app when the user taps the notification.
Scheduling Multiple Notifications
To schedule multiple notifications, simply call the zonedSchedule()
method repeatedly with different IDs, titles, bodies, and timestamps:
for (int i = 0; i < 3; i++) {
final scheduledTime = tz.TZDateTime.now(tz.local).add(Duration(seconds: i * 5));
await flutterLocalNotificationsPlugin.zonedSchedule(
i,
‘Notification $i‘,
‘This is notification number $i‘,
scheduledTime,
platformChannelSpecifics,
uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
androidAllowWhileIdle: true,
);
}
This code schedules 3 notifications, each appearing 5 seconds apart. Note that each notification must have a unique integer ID to avoid conflicts.
Cancelling Notifications
You can cancel a single notification by calling the cancel()
method with the notification‘s ID:
await flutterLocalNotificationsPlugin.cancel(0);
To cancel all pending notifications at once, use the cancelAll()
method:
await flutterLocalNotificationsPlugin.cancelAll();
Cancelling notifications is useful when the event or condition that triggered the notification is no longer relevant. For example, if you have a notification reminding the user to complete a task, you would cancel that notification when the user completes the task in your app.
Notification Channels and Categories
On Android 8.0 (API level 26) and above, you must assign each notification to a channel. Channels let you group notifications by type and give users fine-grained control over notification behavior.
You can create a channel by passing a unique channel ID, name, and description to the AndroidNotificationDetails
constructor:
const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
‘channel ID‘,
‘channel name‘,
‘channel description‘,
importance: Importance.max,
priority: Priority.high,
);
On iOS 10 and above, you can categorize notifications and customize their action buttons using the IOSNotificationDetails
class:
const IOSNotificationDetails iosDetails = IOSNotificationDetails(
categoryIdentifier: ‘CATEGORY_ID‘,
);
You must first register the category ID and its corresponding actions using the requestPermissions()
method:
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
critical: true,
categories: [
IOSNotificationCategory(
identifier: ‘CATEGORY_ID‘,
actions: [
IOSNotificationAction(
identifier: ‘ACTION_ID‘,
title: ‘Action Button‘,
options: IOSNotificationActionOptions.destructive,
),
],
options: [
IOSNotificationCategoryOptions.hiddenPreviewShowTitle,
IOSNotificationCategoryOptions.customDismissAction,
],
),
],
);
This code defines a category with a single action button. When the user taps the button, your app can respond accordingly in the onSelectNotification
callback.
Notification Sounds and Icons
To customize the sound that plays when a notification arrives, you can specify a sound file in the AndroidNotificationDetails
and IOSNotificationDetails
constructors:
const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
‘channel ID‘,
‘channel name‘,
‘channel description‘,
sound: RawResourceAndroidNotificationSound(‘sound_file‘),
);
const IOSNotificationDetails iosDetails = IOSNotificationDetails(
sound: ‘sound_file.aiff‘,
);
On Android, the sound file must be placed in the res/raw
directory. On iOS, it must be in the app bundle root.
To use a custom notification icon on Android, specify the icon file name in the AndroidInitializationSettings
constructor:
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings(‘app_icon‘);
The icon file must be placed in the drawable
directory for each screen density (e.g. drawable-hdpi
, drawable-xhdpi
, etc.).
On iOS, you can set the app badge number in the IOSNotificationDetails
constructor:
const IOSNotificationDetails iosDetails = IOSNotificationDetails(
badgeNumber: 1,
);
Testing and Debugging Notifications
When working with local notifications, it‘s important to test on physical devices to ensure they behave as expected. The iOS and Android simulators may not accurately reflect the notification behavior on real devices.
Some common issues to watch out for:
-
Permissions not granted – Make sure your app requests and receives the necessary permissions before scheduling notifications. Test on devices with different OS versions and permission settings.
-
Invalid icon or sound files – Double-check that your icon and sound files are in the correct format and location for each platform. Incorrect file names or unsupported formats can cause notifications to fail silently.
-
Timezone issues – When scheduling notifications, make sure to use the
tz
package to specify the user‘s local timezone. Scheduling notifications withDateTime.now()
can cause issues for users in different timezones. -
Duplicate or expired notifications – Each notification must have a unique integer ID. Scheduling multiple notifications with the same ID will overwrite the previous one. Similarly, if you schedule a notification with a timestamp in the past, it will fire immediately or not at all.
If notifications aren‘t appearing as expected, you can use the following debugging techniques:
- Check the console output in Android Studio or Xcode for any error messages or stack traces related to the
flutter_local_notifications
plugin. - Use the
print()
ordebugPrint()
statements to log information about your notifications before scheduling them. - Manually trigger notifications with specific IDs, titles, bodies, and payloads to isolate any issues.
- Check the device‘s notification settings to ensure your app hasn‘t been muted or blocked.
- Use the
pendingNotificationRequests()
method to retrieve a list of all scheduled notifications and verify their settings.
By testing thoroughly and monitoring the system log output, you can identify and fix any notification-related issues in your Flutter app.
Conclusion
Local notifications are a powerful way to keep users engaged with your Flutter app, even when they‘re not actively using it. With the flutter_local_notifications
plugin, you can easily schedule, customize, and manage notifications on both Android and iOS.
Some key points to remember:
- Initialize the plugin and request the necessary permissions before scheduling any notifications.
- Use the
zonedSchedule()
method to schedule notifications at a specific date and time, taking into account the user‘s local timezone. - Handle notification taps by providing an
onSelectNotification
callback and using the payload to determine the appropriate action. - Cancel outdated notifications to avoid cluttering the user‘s notification drawer.
- Test on physical devices and monitor the system logs to identify any issues.
By following the tips and examples in this guide, you‘ll be able to add reliable, engaging notifications to your Flutter app in no time. Happy coding!