Loading... Search articles

Search for articles

Sorry, but we couldn't find any matches...

But perhaps we can interest you in one of our more popular articles?
Learn how to set up and display Flutter instant or scheduled notifications on iOS and Android. You’ll also learn how to group messages on both platforms.

How to implement Flutter local notifications to boost user engagement

Aug 8, 2022

In this tutorial, you’ll learn how to implement local notifications using the flutter_local_notifications package. This article is written Ivy Walobwa

Improving your app’s user engagement is key to its success. One way to prevent churn and increase interactions in your Flutter app is by using notifications. Push notifications are pop-up messages that are displayed outside the app’s UI. These messages can appear when the app is closed or in the background. Push notifications are used to get users’ attention and communicate information to them.

We’ll cover how to set up and display instant or scheduled notifications on iOS and Android. You’ll also learn how to group messages on both platforms.

This tutorial assumes that you have a working knowledge of Dart and Flutter.

Example app

You’ll work on the JustWater application shown below, which sends notifications to a user that remind them to drink water. The messages can be sent instantly or scheduled for a specific time. Similar notifications are grouped. Clicking on a single notification navigates to a second screen, as demonstrated in the video below.

Flutter local notifications UI screenshot

Video that demonstrates Flutter local notifications:

Flutter local notifications setup

flutter_local_notifications is a cross-platform plugin for displaying local notifications. The plugin has several features you can implement to customize notifications on the supported platforms. To get started, you’ll need to install and set up the package for Android and iOS.

You can find the initial UI for the application on this DartPad. Copy the code into your main.dart file, and update your assets folder to include an image that you will display on the UI. Update line 49 in your code to refer to the added image, as shown below:

Image.asset("assets/images/justwater.png", scale: 0.6)

Running your application should display the initial state of your app.

JustWater app initial state

Add dependencies

In your pubspec.yaml file, add the dependency as shown below:

flutter_local_notifications: ^9.7.0
timezone: ^0.8.0
rxdart: ^0.27.5
path_provider: ^2.0.11
http: ^0.13.4
flutter_native_timezone: ^2.0.0

The following packages are installed:

  • flutter_local_notifications — display local notifications
  • timezone and flutter_native_timezone — get the local timezone
  • rxdart — create and listen to event streams
  • path_provider — work with the device file system
  • http — make HTTP requests

Platform setup and initialization

First, you’ll set up notifications on iOS by adding the code snippet below in your /ios/Runner/AppDelegate.swift file.

if #available(iOS 10.0, *) {
  UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
}

Then create a notification_service.dart file and add the code snippet below.

import 'dart:io';
import 'dart:ui';

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:flutter_push_notifications/utils/download_util.dart';
import 'package:rxdart/subjects.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;

class NotificationService {
  NotificationService();

  final _localNotifications = FlutterLocalNotificationsPlugin();
  final BehaviorSubject<String> behaviorSubject = BehaviorSubject();

  Future<void> initializePlatformNotifications() async {
    const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('ic_stat_justwater');

    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings(
            requestSoundPermission: true,
            requestBadgePermission: true,
            requestAlertPermission: true,
            onDidReceiveLocalNotification: onDidReceiveLocalNotification);

    final InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );

    await _localNotifications.initialize(initializationSettings,
        onSelectNotification: selectNotification);
  }

  void onDidReceiveLocalNotification(
      int id, String? title, String? body, String? payload) {
    print('id $id');
  }

  void selectNotification(String? payload) {
    if (payload != null && payload.isNotEmpty) {
      behaviorSubject.add(payload);
    }
  }
}

The file creates a new instance of the plugin class and then initializes it with the settings to use for Android and iOS. AndroidInitializationSettings expects the asset’s name to be displayed as the app icon. You should add this in your android/app/src/main/res/drawable folder.

The selectNotification method adds the payload to the data stream event, which its listeners will receive. With that, you are now ready to display your first notification.

Displaying notifications

To display a notification, you’ll first need to describe the details of your notification for each platform. You can set the icons, attachments, notification priority, and more.

Large icon and big picture illustration

Future<NotificationDetails> _notificationDetails() async {
  final bigPicture = await DownloadUtil.downloadAndSaveFile(
      "https://images.unsplash.com/photo-1624948465027-6f9b51067557?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80",
      "drinkwater");

  AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'channel id',
    'channel name',
    groupKey: 'com.example.flutter_push_notifications',
    channelDescription: 'channel description',
    importance: Importance.max,
    priority: Priority.max,
    playSound: true,
    ticker: 'ticker',
    largeIcon: const DrawableResourceAndroidBitmap('justwater'),
    styleInformation: BigPictureStyleInformation(
      FilePathAndroidBitmap(bigPicture),
      hideExpandedLargeIcon: false,
    ),
    color: const Color(0xff2196f3),
  );

  IOSNotificationDetails iosNotificationDetails = IOSNotificationDetails(
      threadIdentifier: "thread1",
      attachments: <IOSNotificationAttachment>[
        IOSNotificationAttachment(bigPicture)
      ]);

  final details = await _localNotifications.getNotificationAppLaunchDetails();
  if (details != null && details.didNotificationLaunchApp) {
    behaviorSubject.add(details.payload!);
  }
  NotificationDetails platformChannelSpecifics = NotificationDetails(
      android: androidPlatformChannelSpecifics, iOS: iosNotificationDetails);

  return platformChannelSpecifics;
}

The code snippet above downloads and saves the big picture locally on the device. You can find the downloadAndSaveFile function on this gist. In this case, you set the Android largeIcon from a drawable resource, android/app/src/main/res/drawable/justwater.png. The color of the Android notification details sets the color of the app icon.

Suppose a user clicks on a notification when the app is no longer in the background; the notification payload is added to the data event on app launch and initialized with the details set for both platforms.

You need to call the showLocalNotification method with the notification details to show a notification. The code snippet below displays a notification with a title, body, payload, and platform-specific configurations.

Future<void> showLocalNotification({
  required int id,
  required String title,
  required String body,
  required String payload,
}) async {
  final platformChannelSpecifics = await _notificationDetails();
  await _localNotifications.show(
    id,
    title,
    body,
    platformChannelSpecifics,
    payload: payload,
  );
}

To invoke showLocalNotification from the UI, create an instance of your notification service and initialize the platform-specific settings. You then need to listen to the data event and configure an action once data is received. In this case, you’ll navigate to a second screen, passing the payload received, as shown below:

late final NotificationService notificationService;
@override
void initState() {
  notificationService = NotificationService();
  listenToNotificationStream();
  notificationService.initializePlatformNotifications();
  super.initState();
}

void listenToNotificationStream() =>
    notificationService.behaviorSubject.listen((payload) {
      Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => MySecondScreen(payload: payload)));
});

The code snippet above is added to your main.dart file in the _MyHomePageState widget.

Finally, you call the showLocalNotification method on button press to display the notification. The notification ID, title, body, and payload are passed as arguments.

ElevatedButton(
  onPressed: () async {
    await notificationService.showLocalNotification(
        id: 0,
        title: "Drink Water",
        body: "Time to drink some water!",
        payload: "You just took water! Huurray!");
  },
  child: const Text("Drink Now"),
),

Displaying notification

Clicking on the notification navigates to the second screen displaying the payload attached.

Second screen with payload

Display scheduled notifications

You can schedule notifications to be displayed after a specific duration. Scheduling notifications will require you to set a date and time relative to a particular timezone.

In the initializePlatformNotifications method, add the code snippet below to initialize timezones and get and set the local timezone.

tz.initializeTimeZones();
  tz.setLocalLocation(
    tz.getLocation(
      await FlutterNativeTimezone.getLocalTimezone(),
    ),
  );

...

Then make use of the zonedSchedule function to show the notification at the specified time relative to the specific timezone.

Future<void> showScheduledLocalNotification({
  required int id,
  required String title,
  required String body,
  required String payload,
  required int seconds,
}) async {
  final platformChannelSpecifics = await _notificationDetails();
  await _localNotifications.zonedSchedule(
    id,
    title,
    body,
    tz.TZDateTime.now(tz.local).add(Duration(seconds: seconds)),
    platformChannelSpecifics,
    payload: payload,
    uiLocalNotificationDateInterpretation:
        UILocalNotificationDateInterpretation.absoluteTime,
    androidAllowWhileIdle: true,
  );
}

You can display periodic notifications using the periodicallyShow method. These are displayed periodically after a specified time.

Future<void> showPeriodicLocalNotification({
  required int id,
  required String title,
  required String body,
  required String payload,
}) async {
  final platformChannelSpecifics = await _notificationDetails();
  await _localNotifications.periodicallyShow(
    id,
    title,
    body,
    RepeatInterval.everyMinute,
    platformChannelSpecifics,
    payload: payload,
    androidAllowWhileIdle: true,
  );
}

The code snippet below schedules notifications to be sent every two seconds.

ElevatedButton(
    onPressed: () async {
      await notificationService.showScheduledLocalNotification(
          id: 1,
          title: "Drink Water",
          body: "Time to drink some water!",
          payload: "You just took water! Huurray!",
          seconds: 2);
    },
    child: const Text("Schedule Drink"))

Display grouped notifications

If four or more similar notifications are displayed, you can group them based on a group key on Android or a thread identifier on iOS.

To display grouped notifications on Android, you’ll create another notification detail with the inbox-style notification. The notification detail will describe the group summary for your notifications supporting older versions. You also need to set the setAsGroupSummary property to true. You can learn more about grouping Android notifications from the official Android documentation.

To display grouped notifications on iOS, you need to give our notification a thread identifier.

Future<NotificationDetails> _groupedNotificationDetails() async {
  const List<String> lines = <String>[
    'group 1 First drink',
    'group 1   Second drink',
    'group 1   Third drink',
    'group 2 First drink',
    'group 2   Second drink'
  ];
  const InboxStyleInformation inboxStyleInformation = InboxStyleInformation(
      lines,
      contentTitle: '5 messages',
      summaryText: 'missed drinks');
  AndroidNotificationDetails androidPlatformChannelSpecifics =
      const AndroidNotificationDetails(
    'channel id',
    'channel name',
    groupKey: 'com.example.flutter_push_notifications',
    channelDescription: 'channel description',
    setAsGroupSummary: true,
    importance: Importance.max,
    priority: Priority.max,
    playSound: true,
    ticker: 'ticker',
    styleInformation: inboxStyleInformation,
    color: Color(0xff2196f3),
  );

  const IOSNotificationDetails iosNotificationDetails =
      IOSNotificationDetails(threadIdentifier: "thread2");

  final details = await _localNotifications.getNotificationAppLaunchDetails();
  if (details != null && details.didNotificationLaunchApp) {
    behaviorSubject.add(details.payload!);
  }

  NotificationDetails platformChannelSpecifics = NotificationDetails(
      android: androidPlatformChannelSpecifics, iOS: iosNotificationDetails);

  return platformChannelSpecifics;
}

You then call the show method to display the notifications. Each notification will have a unique ID. On Android, the notification meant to be the group summary will have a custom title and body, which displays an outline for the grouping.

Future<void> showGroupedNotifications({
  required String title,
}) async {
  final platformChannelSpecifics = await _notificationDetails();
  final groupedPlatformChannelSpecifics = await _groupedNotificationDetails();
  await _localNotifications.show(
    0,
    "group 1",
    "First drink",
    platformChannelSpecifics,
  );
  await _localNotifications.show(
    1,
    "group 1",
    "Second drink",
    platformChannelSpecifics,
  );
  await _localNotifications.show(
    3,
    "group 1",
    "Third drink",
    platformChannelSpecifics,
  );
  await _localNotifications.show(
    4,
    "group 2",
    "First drink",
    Platform.isIOS
        ? groupedPlatformChannelSpecifics
        : platformChannelSpecifics,
  );
  await _localNotifications.show(
    5,
    "group 2",
    "Second drink",
    Platform.isIOS
        ? groupedPlatformChannelSpecifics
        : platformChannelSpecifics,
  );
  await _localNotifications.show(
    6,
    Platform.isIOS ? "group 2" : "Attention",
    Platform.isIOS ? "Third drink" : "5 missed drinks",
    groupedPlatformChannelSpecifics,
  );
}

Hooking the showGroupedNotifications method to your UI will display the notifications added.

Grouped notifications

For older Android versions, the default inbox styling will be displayed as shown below:

Grouped Android older versions

Cancel notifications

To cancel a single notification, make use of the cancel method, passing the ID of the notification. You can use the cancelAll method to cancel all notifications.

void cancelSingleNotifications() => _localNotifications.cancel(0);
void cancelAllNotifications() => _localNotifications.cancelAll();

Build and share your app artifacts with Codemagic

Codemagic is a CI/CD tool for mobile app projects. It works by automatically handling all the builds, tests, and releases for you.

To build and share your app artifacts with Codemagic, you first need to have your Flutter app hosted on your favorite Git provider. Prepare your app for release by following the guide on the official Flutter documentation.

You need a Codemagic account to use the CI/CD tool. You can sign up for Codemagic with your favorite Git provider.

Sign up

After signing up, you can get set up by following the steps below:

  1. Create an application and connect a repository from your Git provider. Connect repository

  2. Select your project repository and type. In this case, the project type is Flutter App (via WorkFlow Editor). Select project

Your app is ready, and you can now add some settings to determine how your app is built.

To build your app, you need to customize the build settings to suit it:

  1. Click on “Finish build setup” for first-time applications. For an existing app, click on the settings icon.

  2. Select “Android” as your build platform on the Workflow Editor page. Select build platform

  3. Expand the “Build triggers” section and select your preferred build trigger. You can also configure the watched branches and tags. These triggers will start an app build whenever they occur. Select build trigger

  4. Expand the “Build” section and select your app’s build format and mode.

Select build format

  1. Save the changes and start a new build. Codemagic will add a green checkmark beside your app name if your app builds successfully. Downloadable Android artifacts are also added.

Build app

Congrats! You just made your first build with Codemagic and can now download and share your app artifacts.

Conclusion

This tutorial taught you to set up local notifications for Android and iOS. You learned to display local notifications and add notification details for both platforms. We also covered how to display scheduled and periodic notifications and grouping notifications. Finally, you learned to build and share your app artifacts with Codemagic.

You can find the application used in this article on GitHub. We hope you enjoyed this tutorial!


This article is written by Ivy Walobwa, a Flutter Developer and Technical Author. She’s passionate about communities and always willing to help facilitate students’ learning in the tech space. If she’s not building communities or creating content, she’s probably hiking on a random trail. Check out her profile.

How did you like this article?

Oops, your feedback wasn't sent

Related articles

Latest articles

Show more posts