SlimenTN
SlimenTN

Reputation: 3564

Flutter: Push notifications even if the app is closed

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?

Upvotes: 59

Views: 126461

Answers (7)

Covenant T. Junior
Covenant T. Junior

Reputation: 66

I'll recommend two options. The first is to use Firebase Cloud Messaging (FCM) for push notifications. FCM is a reliable and scalable solution provided by Google that allows you to send notifications to your app's users across multiple platforms, including Android and iOS. You can easily integrate FCM into your Flutter app using the firebase_messaging package, which provides a Flutter-friendly API for sending and receiving push notifications.

The second option is to use flutter_local_notifications along with flutter_background_service. flutter_local_notifications is a Flutter plugin that allows you to display local notifications on the user's device, while flutter_background_service enables you to run background tasks or services in your Flutter app, even when the app is not in the foreground. By combining these two plugins, you can create a custom solution for scheduling and displaying notifications within your app, giving you more control over the notification content and behavior.

Both options have their pros and cons, so you'll need to consider factors such as your app's requirements, budget, and development expertise before choosing the best approach for your project. If you require real-time notifications or need to reach a large number of users, Firebase Cloud Messaging may be the better choice. However, if you prefer more control over the notification experience or need to perform background tasks in your app, using local notifications with background services may be a more suitable option.

Upvotes: 1

Abdalmuneim Mahmoud
Abdalmuneim Mahmoud

Reputation: 167

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 scheduledNotificationmethod

Upvotes: 0

Osama Buzdar
Osama Buzdar

Reputation: 1252

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

Upvotes: 0

a0x2
a0x2

Reputation: 2121

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.

Upvotes: 55

Gautam Kumar
Gautam Kumar

Reputation: 399

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();
}

Upvotes: 16

Tushar Pandey
Tushar Pandey

Reputation: 4867

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}}

Upvotes: 3

PrakashKing
PrakashKing

Reputation: 713

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);

Upvotes: 0

Related Questions