I have built an application with flutter that works like a reminder.
How can I display notifications to the user even though the app is closed?
6 Answers
For reminders i would recomend Flutter Local Notifications Plugin. It has a powerful scheduling api. From the documentation of local notification:
Scheduling when notifications should appear - Periodically show a notification (interval-based) - Schedule a notification to be shown daily at a specified time - Schedule a notification to be shown weekly on a specified day and time - Ability to handle when a user has tapped on a notification when the app is the foreground, background or terminated
And for push notification, you can use Firebase Cloud Messaging or one signal plugin or you can implement natively through platform-channels
Edit: You can also fire notifications according to specific conditions even if the app is terminated. This can be achevied by running dart code in the background. Quoting from the official faq:
Can I run Dart code in the background of an Flutter app? Yes, you can run Dart code in a background process on both iOS and Android. For more information, see the Medium article Executing Dart in the Background with Flutter Plugins and Geofencing.

- 1,995
- 1
- 18
- 26
-
2FlutterLocalNotificationsPlugin works very nicely. But I can't find how to "do something" when the scheduled notification fires off. Any idea? – Dpedrinha Sep 04 '19 at 03:28
-
1Any Working complete example working with flutter and Flutter Local Notifications Plugin to display notifications even if app is terminated? – ElLocoCocoLoco Dec 03 '21 at 09:48
-
1my users create events with some dates. How could I send notifications based on these dates to these users? – Dani Aug 09 '22 at 09:53
I have found a solution to this problem. We just have to register the Local Notification Plugin in the Application class.
First Create a class FlutterLocalNotificationPluginRegistrant, I have created this in Kotlin.
class FlutterLocalNotificationPluginRegistrant {
companion object {
fun registerWith(registry: PluginRegistry) {
if (alreadyRegisteredWith(registry)) {
Log.d("Local Plugin", "Already Registered");
return
}
FlutterLocalNotificationsPlugin.registerWith(registry.registrarFor("com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin"))
Log.d("Local Plugin", "Registered");
}
private fun alreadyRegisteredWith(registry: PluginRegistry): Boolean {
val key = FlutterLocalNotificationPluginRegistrant::class.java.canonicalName
if (registry.hasPlugin(key)) {
return true
}
registry.registrarFor(key)
return false
}
}}
Now create a Application class extending FlutterApplication and implement PluginRegistry.PluginRegistrantCallback.
class Application : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
}
override fun registerWith(registry: PluginRegistry?) {
if (registry != null) {
FlutterLocalNotificationPluginRegistrant.registerWith(registry)
}
}}
and register the Application class in the AndroidManifest.xml
<application
android:name="com.packagename.Application"/>
All done. Now write a function to show notification and call it from the background handler method of Firebase messaging.
Future _showNotificationWithDefaultSound(String title, String message) async {
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'channel_id', 'channel_name', 'channel_description',
importance: Importance.Max, priority: Priority.High);
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
'$title',
'$message',
platformChannelSpecifics,
payload: 'Default_Sound',
);
}
and call it like this.
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) async {
if (message['data'] != null) {
final data = message['data'];
final title = data['title'];
final body = data['message'];
await _showNotificationWithDefaultSound(title, message);
}
return Future<void>.value();
}

- 351
- 4
- 7
-
2One question: Is this background message handler of firebase receive data messages even when the app is removed from recent list? If no, did you found any workaround for that?? – krishnakumarcn Jan 23 '20 at 05:59
-
@KrishnakumarCN if you send only data object from FCM then you will receive data message in the background message handler, but if you also send notification object then FCM will automatically display the notification and then on click, you will get data message in onResume (if the app is in the background) or in on launch method (If app is not in the recent lists) – Gautam Kumar Feb 12 '20 at 05:06
-
1
-
1@MalekTubaisaht Please check out this repository. Please do the FCM configuration part first. https://github.com/gssinghgautam/flutter_notification_demo.git – Gautam Kumar May 24 '20 at 13:48
-
-
-
@SajadJaward I think the idea here rely on on registering the same operation on a native background side so yeah I think it's possible you just need to create a plugin registrant file relevant to the FirebaseCloudMessagingPluginRegistrant.kt with one signal plugin in it – Amr Ahmed Feb 13 '22 at 17:07
-
-
my users create events with some dates. How could I send notifications based on these dates to these users? – Dani Aug 09 '22 at 09:53
I have also faced this issue, So these are my learnings
In my Case : i am able to get notification in App-Resume or App-background state, but in App-Close state, I am not receiving notifification.
In this case our notification body was :
{notification: {body: null, title: null}, data: {body: hello, title: world}}
To Receive Notification in App-Closed state we changed notification to
{notification: {body: abc, title: abc}, data: {url: string, body: string, title: string}}

- 4,557
- 4
- 33
- 50
-
I think the best way to handle notification in the background, is to let the FCM automatically display the notification from the notification object and on click fetch the data object in the ONLAUNCH OR ONRESUME method of the FCM. – Gautam Kumar Feb 12 '20 at 05:08
You can use scheduled notifications in flutter.
var scheduledNotificationDateTime =
new DateTime.now().add(new Duration(seconds: 5));
var androidPlatformChannelSpecifics =
new AndroidNotificationDetails('your other channel id',
'your other channel name', 'your other channel description');
var iOSPlatformChannelSpecifics =
new IOSNotificationDetails();
NotificationDetails platformChannelSpecifics = new
NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.schedule(
0,
'scheduled title',
'scheduled body',
scheduledNotificationDateTime,
platformChannelSpecifics);

- 635
- 2
- 11
- 24
-
Thanks for your answer but in my case I don't want the notifications to be triggered by itself I want the app to show notifications based on some conditions, and to achieve this I need my app to work on background even tho it's closed. – SlimenTN Dec 02 '18 at 12:58
-
for that you need to have connected to backend databases like firebase, there you can manage notifications to send over internet.(to all users common/specific) – PrakashKing Dec 02 '18 at 15:01
-
2@SlimenTN Follow my solution, I have the same use case like yours. It's working!!! – Gautam Kumar Jan 21 '20 at 08:20
-
my users create events with some dates. How could I send notifications based on these dates to these users? – Dani Aug 09 '22 at 09:53
For those who are using the latest version around 2.2 just call the firebaseMessageInstance
FirebaseMessaging.instance.getInitialMessage().then((message) =>
message.messageId.isNotEmpty
? print('we can now navigate to specific screen')
: print('there is no new notification so default screen will be shown when application start from terminated state'));
Don't forget to call the
Navigator.push(
context, MaterialPageRoute(builder: (context) => YourScreenName()));
when message.messageId.isNotEmpty
upvote if you like this approach thanks have a good coding day

- 1,115
- 10
- 20
If you do not need to connect to the Internet, you can use this packages flutter local notification && flutter native timezone
after add the package to pubspace.ymal
add this code to android/app/src/main/AndroidManifest.xml
<activity
android:showWhenLocked="true"
android:turnScreenOn="true">
also in ios folder open if you used swift Runner/AppDelegate.swift
in function didFinishLaunchingWithOptions add
if #available(iOS 10.0, *) {UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate}
if you used Object-C Runner/AppDelegate.m
in function didFinishLaunchingWithOptions add
if (@available(iOS 10.0, *)) {[UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
}
after that you should add app-icon
to drawable folder
then import the packages import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz;
in file dart create and add
class NotifyHelper {
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
String selectedNotificationPayload = '';
final BehaviorSubject<String> selectNotificationSubject =
BehaviorSubject<String>();
initializeNotification() async {
tz.initializeTimeZones();
_configureSelectNotificationSubject();
await _configureLocalTimeZone();
// await requestIOSPermissions(flutterLocalNotificationsPlugin);
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(
requestSoundPermission: false,
requestBadgePermission: false,
requestAlertPermission: false,
onDidReceiveLocalNotification: onDidReceiveLocalNotification,
);
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('appicon');
final InitializationSettings initializationSettings =
InitializationSettings(
iOS: initializationSettingsIOS,
android: initializationSettingsAndroid,
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: (String? payload) async {
if (payload != null) {
debugPrint('notification payload: ' + payload);
}
selectNotificationSubject.add(payload!);
},
);
}
displayNotification({required String title, required String body}) async {
print('doing test');
var androidPlatformChannelSpecifics = const AndroidNotificationDetails(
'your channel id', 'your channel name', 'your channel description',
importance: Importance.max, priority: Priority.high);
var iOSPlatformChannelSpecifics = const IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(
android: androidPlatformChannelSpecifics,
iOS: iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(
0,
title,
body,
platformChannelSpecifics,
payload: 'Default_Sound',
);
}
// this is the scheduled notification
// Task is a model class have a data item like title, desc, start time and end time
scheduledNotification(int hour, int minutes, Task task) async {
await flutterLocalNotificationsPlugin.zonedSchedule(
task.id!,
task.title,
task.note,
//tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
_nextInstanceOfTenAM(hour, minutes),
const NotificationDetails(
android: AndroidNotificationDetails(
'your channel id', 'your channel name', 'your channel description'),
),
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
payload: '${task.title}|${task.note}|${task.startTime}|',
);
}
tz.TZDateTime _nextInstanceOfTenAM(int hour, int minutes) {
final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
tz.TZDateTime scheduledDate =
tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minutes);
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
return scheduledDate;
}
void requestIOSPermissions() {
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
Future<void> _configureLocalTimeZone() async {
tz.initializeTimeZones();
final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(timeZoneName));
}
/* Future selectNotification(String? payload) async {
if (payload != null) {
//selectedNotificationPayload = "The best";
selectNotificationSubject.add(payload);
print('notification payload: $payload');
} else {
print("Notification Done");
}
Get.to(() => SecondScreen(selectedNotificationPayload));
} */
//Older IOS
Future onDidReceiveLocalNotification(
int id, String? title, String? body, String? payload) async {
// display a dialog with the notification details, tap ok to go to another page
/* showDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: const Text('Title'),
content: const Text('Body'),
actions: [
CupertinoDialogAction(
isDefaultAction: true,
child: const Text('Ok'),
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Container(color: Colors.white),
),
);
},
)
],
),
);
*/
Get.dialog( Text(body!));
}
//I used Get package Get here to go screen notification
void _configureSelectNotificationSubject() {
selectNotificationSubject.stream.listen((String payload) async {
debugPrint('My payload is ' + payload);
await Get.to(() => NotificationScreen(payload));
});
}
}
use object from this class and call the scheduledNotification
method

- 117
- 1
- 3
-
-
1yes, But if you don't open the app after a while, it won't work – Abdalmuneim Mahmoud Jun 14 '22 at 08:38