Notifications alert users to important information about the apps and services they are subscribed to. They are aimed at improving user experience and driving engagement within an application.
When it comes to mobile apps, there are two types of notifications, push notifications and local notifications. In this article, we’ll implement local notifications into both Android and iOS platforms using the flutter_local_notifications package.
Using local notifications is a way to engage with your users and draw their attention back to your application without the use of an internet connection, and apps like Reminder and to-do apps make heavy use of them. They are generally pre-scheduled and fire when certain actions are performed by the user in the app.
The major difference between local and push notifications is that local notifications are scheduled by an app locally and are delivered by the same device, whereas push notifications are sent from a remote server. Let’s build out a project so you can see how local notifications work.
The first step is to run the command below in your terminal to add the latest version of flutter_local_notifications to your pubspec.yaml
file.
//run this command in the terminal $ flutter pub add flutter_local_notifications
Next, create a new Dart file named notification_service.dart
. You can give it any file name you want, but I prefer to name files according to their functionality.
In Flutter, it’s best practice to seclude your logic from your UI. To do this, we’ll create a class called NotificationService
in the notification_service.dart
file. This class will handle all the notification logic and expose methods to create, send, schedule, and cancel notifications.
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; class NotificationService { //Singleton pattern static final NotificationService _notificationService = NotificationService._internal(); factory NotificationService() { return _notificationService; } NotificationService._internal(); //instance of FlutterLocalNotificationsPlugin final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); }
Let’s analyze what’s happening in the code above:
We used the singleton pattern to create the NotificationService
class. In addition, on line 12, we created an instance of FlutterLocalNotificationsPlugin
, which initializes Flutter local notification settings for the Android and iOS platforms.
Let’s walk through how we can configure initialization settings for both Android and iOS platforms.
Configuring Android initialization settings
To configure the Android initialization settings, we need to pass in a single required argument, which is the app icon that would be displayed in the notification bar:
final AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('app_icon');
Now we need to add our icon as a drawable resource to the Android head project. Here’s the full path for doing this:
YOUR_APPLICATION_FOLDER_NAME\android\app\src\main\res\drawable\YOUR_APP_ICON.png
Configuring initialization settings for iOS
Configuring these settings for iOS is a little more complicated because we have to consider the multiple ways in which notifications are handled across different versions of the iOS operating system.
First, add the following lines to the didFinishLaunchingWithOptions
method in the AppDelegate.swift
file of your iOS project.
if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate }
Our AppDelegate.swift
file should look like this:
import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
The IOSInitializationSettings
object takes in three arguments: requestSoundPermission
, requestBadgePermission
, and requestAlertPermission
. These arguments control which permission is being requested from the user.
Depending on your use case, you can choose to set all notification permissions to false
, then call the requestIOSPermissions
method with desired permissions at the appropriate point in your application, as shown below.
//Initialization Settings for iOS devices final IOSInitializationSettings initializationSettingsIOS = IOSInitializationSettings( requestSoundPermission: false, requestBadgePermission: false, requestAlertPermission: false, ); void requestIOSPermissions( FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin) { flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< IOSFlutterLocalNotificationsPlugin>() ?.requestPermissions( alert: true, badge: true, sound: true, ); }
InitializationSettings
objectThe next step is to create an InitializationSettings
object. This plugin is used to initialize settings for both Android and iOS platforms.
Generally, the InitializationSettings
has three named optional parameters, android
, iOS
, and macOS
, and they take in the corresponding platform initialization settings arguments.
final InitializationSettings initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
Following the configuration of our platform-specific initialization settings, we would create the method init
, which would contain all of our initialization settings logic and be called from our main.dart
file upon app launch.
Future<void> init() async { //Initialization Settings for Android final AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('app_icon'); //Initialization Settings for iOS final IOSInitializationSettings initializationSettingsIOS = IOSInitializationSettings( requestSoundPermission: false, requestBadgePermission: false, requestAlertPermission: false, ); //InitializationSettings for initializing settings for both platforms (Android & iOS) final InitializationSettings initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsIOS); await flutterLocalNotificationsPlugin.initialize( initializationSettings, ); }
In the code above, we pass in our platform-specific initialization settings into the InitializationSettings
object.
The next step is to call the initialize
method on the FlutterLocalNotificationsPlugin
object. This method takes in two arguments, the InitializationSettings
object and the onSelectNotification
property.
The onSelectNotification
property takes in a callback function that will be triggered when the notification is tapped. This function holds a single required argument called the payload
, which holds any data that is passed through the notification.
Future selectNotification(String payload) async { await Navigator.push( context, MaterialPageRoute<void>(builder: (context) => SecondScreen(payload)), ); }
Here, this callback function will trigger navigation to SecondScreen
and display the payload
associated with the notification when the user taps on the notification.
Our init
method should look like this now:
Future<void> init() async { //Initialization Settings for Android final AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('app_icon'); //Initialization Settings for iOS final IOSInitializationSettings initializationSettingsIOS = IOSInitializationSettings( requestSoundPermission: false, requestBadgePermission: false, requestAlertPermission: false, ); //InitializationSettings for initializing settings for both platforms (Android & iOS) final InitializationSettings initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsIOS); await flutterLocalNotificationsPlugin.initialize( initializationSettings, onSelectNotification: selectNotification ); } Future selectNotification(String payload) async { await Navigator.push( context, MaterialPageRoute<void>(builder: (context) => SecondScreen(payload)), ); }
Let’s return to our main.dart
file. In the main
function, we will call the init
method and the requestiOSPermissions
method to request permissions from the user as soon as the app starts on iOS devices.
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await NotificationService().init(); // await NotificationService().requestIOSPermissions(); // runApp(MyApp()); }
To display a notification, we need to create a platform-specific NotificationDetails
instance, which takes in arguments that are unique to each platform.
AndroidNotificationDetails
handles the configuration of notifications in Android devices. It takes in several arguments, like channelID
, channelName
, channelDescription
, priority
, importance
, and so on.
The iOSNotificationDetails
handles the configuration of notifications in iOS devices and it takes in arguments such as presentAlert
, presentBadge
, badgeNumber
, subtitle
, sound
, etc.
Here’s what the AndroidNotificationDetails
instance looks like:
AndroidNotificationDetails _androidNotificationDetails = AndroidNotificationDetails( 'channel ID', 'channel name', 'channel description', playSound: true, priority: Priority.high, importance: Importance.high, );
Here’s what the iOSNotificationDetails
instance looks like:
IOSNotificationDetails _iosNotificationDetails = IOSNotificationDetails( presentAlert: bool?, presentBadge: bool?, presentSound: bool?, badgeNumber: int? attachments: List<IOSNotificationAttachment>? subtitle: String?, threadIdentifier: String? );
Now the next step is to create a NotificationDetails
object that takes in our platform-specific notification details objects as arguments.
const NotificationDetails platformChannelSpecifics = NotificationDetails( android: _androidNotificationDetails, iOS: _iOSNotificationDetails);
Next we need to call the show
method of the FlutterLocalNotificationsPlugin
. The show
method is responsible for creating push notifications, and it expects some arguments like id
, title
, body
, notificationDetails
, and payload
.
id
: unique identifier of a notification
title
: title for the notification
body
: the notification message
notificationDetails
: where we pass in thenotificationDetails
object
payload
: holds the data that is passed through the notification when the notification is tapped
await flutterLocalNotificationsPlugin.show( 0, 'Notification Title', 'This is the Notification Body', platformChannelSpecifics, payload: 'Notification Payload', );
Now, let’s create a showNotification
method and wrap all of this logic in it, then we can call this method from anywhere to display a notification.
class NotificationService { .... Future<void> showNotifications() async { await flutterLocalNotificationsPlugin.show( 0, 'Notification Title', 'This is the Notification Body', platformChannelSpecifics, payload: 'Notification Payload', ); } }
To schedule a notification, we need to call the zoneSchedule
method of the FlutterLocalNotificationsPlugin
. This method expects an instance of TZDateTime
class, which is provided by the timezone
package.
Because the flutter_local_notifications
plugin already depends on the timezone
package, there’s no need to add the timezone
package as a dependency in our pubspec.yaml
file. We only have to import it into our notification_service.dart
file and initialize it.
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; .... Future<void> init() async { final AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('app_icon'); final IOSInitializationSettings initializationSettingsIOS = IOSInitializationSettings( requestSoundPermission: false, requestBadgePermission: false, requestAlertPermission: false, ); final InitializationSettings initializationSettings = InitializationSettings( android: initializationSettingsAndroid, iOS: initializationSettingsIOS); //initialize timezone package here tz.initializeTimeZones(); // <---- await flutterLocalNotificationsPlugin.initialize( initializationSettings, onSelectNotification: selectNotification ); }
The zoneSchedule
method takes in several arguments, including id
, title
, body
, scheduledDate
, notificationDetails
, payload
, uiLocalNotificationDateInterpretation
, and androidAllowWhileIdle
.
The
scheduleDate
parameter specifies when a notification should be displayed.androidAllowWhileIdle
, when set to betrue
, ensures scheduled notifications are displayed regardless of if the device is in low-power mode.
await flutterLocalNotificationsPlugin.zonedSchedule( 0, "Notification Title", "This is the Notification Body!", tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)), platformChannelSpecifics, androidAllowWhileIdle: true, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime);
Now, let’s create a scheduleNotification
method and wrap all of this logic in it, then we can call this method from anywhere to create a scheduled notification.
class NotificationService { .... Future<void> scheduleNotifications() async { await flutterLocalNotificationsPlugin.zonedSchedule( 0, "Notification Title", "This is the Notification Body!", tz.TZDateTime.now(tz.local).add(const Duration(minutes: 5)), platformChannelSpecifics, androidAllowWhileIdle: true, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime); } }
When canceling a notification, you can either cancel a specific notification or cancel all pending notifications. Let’s walk through how you can do that:
Canceling a single notification
To cancel a specific notification, let’s create a new method called cancelNotification
, which will contain the cancel
method from the FlutterLocalNotificationsPlugin
object. This method expects an argument, which is the id
of the notification.
class NotificationService { .... Future<void> cancelNotifications() async { await flutterLocalNotificationsPlugin.cancel(NOTIFICATION_ID); } }
Canceling all notifications
To cancel all pending notifications, let’s create a new method cancelAllNotifications
, which will contain the cancelAll
method from the FlutterLocalNotificationsPlugin
object.
Unlike cancelling a single notification where its method takes in a single argument, this method doesn’t take in any argument.
class NotificationService { .... Future<void> cancelAllNotifications() async { await flutterLocalNotificationsPlugin.cancelAll(); } }
Here’s a GitHub repository that contains all of the code from this tutorial. If you’d like to see the final build, simply clone that repository and run it on your computer.
Local notifications are incredibly useful for notifying or alerting users about important information, and they can be implemented without an internet connection.
You can read the flutter_local_notifications package documentation to learn other ways you can use local notification in your projects.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
One Reply to "Implementing local notifications in Flutter"
You explaned it beautifully, I previously wrote very ugly global code refering to the docs of local_notifications but you helped me in cleaning up the mess. Thanks a lot, really appreciate your efforts!