[Flutter] Local push notification

2023-03-18 hit count image

Let's see how to use flutter_local_notifications to send push notifications at a specific date time from the user's own device on Flutter.

Outline

In this blog post, I will introduce how to use flutter_local_notifications to send push notifications at a specific date time from the user’s own device on Flutter.

You can see full source code of this blog post on the link below.

Create Flutter project

To check how to use flutter_local_notifications on Flutter, execute the following command to create a new Flutter project.

flutter create flutter_local_notifications_example

Install flutter_local_notifications

To use flutter_local_notifications, we need to execute the following command to install flutter_local_notifications and additional required packages.

flutter pub add flutter_local_notifications flutter_native_timezone flutter_app_badger
  • flutter_native_timezone: Pacakge to show the message at the specific date time.
  • flutter_app_badger: Pacakge to initialize the app icon badge.

Next, let’s see how to use flutter_local_notifications.

flutter_local_notifications Android configuration

To use flutter_local_notifications on Android, open the android/app/build.gradle file and modify it like the following.

...
android {
  compileSdkVersion 33

  compileOptions {
    ...
    coreLibraryDesugaringEnabled true
  }
  defaultConfig {
      ...
      multiDexEnabled true
  }
  ...
}

dependencies {
  ...
  implementation "androidx.multidex:multidex:2.0.1"
  coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
}

Example code

To see how to use flutter_local_notifications, open the ./lib/main.dart file and modify it like the below.

import 'package:flutter/material.dart';
import 'package:flutter_app_badger/flutter_app_badger.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addObserver(this);
    _init();
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      FlutterAppBadger.removeBadge();
    }
  }

  Future<void> _init() async {
    await _configureLocalTimeZone();
    await _initializeNotification();
  }

  Future<void> _configureLocalTimeZone() async {
    tz.initializeTimeZones();
    final String? timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
    tz.setLocalLocation(tz.getLocation(timeZoneName!));
  }

  Future<void> _initializeNotification() async {
    const DarwinInitializationSettings initializationSettingsIOS =
        DarwinInitializationSettings(
      requestAlertPermission: false,
      requestBadgePermission: false,
      requestSoundPermission: false,
    );
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('ic_notification');

    const InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );
    await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
  }

  Future<void> _cancelNotification() async {
    await _flutterLocalNotificationsPlugin.cancelAll();
  }

  Future<void> _requestPermissions() async {
    await _flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            IOSFlutterLocalNotificationsPlugin>()
        ?.requestPermissions(
          alert: true,
          badge: true,
          sound: true,
        );
  }

  Future<void> _registerMessage({
    required int hour,
    required int minutes,
    required message,
  }) async {
    final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
    tz.TZDateTime scheduledDate = tz.TZDateTime(
      tz.local,
      now.year,
      now.month,
      now.day,
      hour,
      minutes,
    );

    await _flutterLocalNotificationsPlugin.zonedSchedule(
      0,
      'flutter_local_notifications',
      message,
      scheduledDate,
      NotificationDetails(
        android: AndroidNotificationDetails(
          'channel id',
          'channel name',
          importance: Importance.max,
          priority: Priority.high,
          ongoing: true,
          styleInformation: BigTextStyleInformation(message),
          icon: 'ic_notification',
        ),
        iOS: const DarwinNotificationDetails(
          badgeNumber: 1,
        ),
      ),
      androidAllowWhileIdle: true,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      matchDateTimeComponents: DateTimeComponents.time,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Local Notifications'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            await _cancelNotification();
            await _requestPermissions();

            final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
            await _registerMessage(
              hour: now.hour,
              minutes: now.minute + 1,
              message: 'Hello, world!',
            );
          },
          child: const Text('Show Notification'),
        ),
      ),
    );
  }
}

Analyze code

To check how to use flutter_local_notifications, let’s see the example code one by one.

Initialize badge

The flutter_local_notifications package doens’ provide the app icon badge initialization code. So, we need to use the FlutterAppBader package to initialize the app icon badge when the app becomes the Foreground state.

...
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  ...
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addObserver(this);
    ...
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      FlutterAppBadger.removeBadge();
    }
  }
  ...
}

Initialize flutter_local_notification

To use flutter_local_notification to send messages at the specific date time, we need to initialize the flutter_local_notifications package like the below.

...
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  @override
  void initState() {
    super.initState();
    ...
    _init();
  }

  ...

  Future<void> _init() async {
    await _configureLocalTimeZone();
    await _initializeNotification();
  }

  Future<void> _configureLocalTimeZone() async {
    tz.initializeTimeZones();
    final String? timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
    tz.setLocalLocation(tz.getLocation(timeZoneName!));
  }

  Future<void> _initializeNotification() async {
    const DarwinInitializationSettings initializationSettingsIOS =
        DarwinInitializationSettings(
      requestAlertPermission: false,
      requestBadgePermission: false,
      requestSoundPermission: false,
    );
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('ic_notification');

    const InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );
    await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
  }
  ...
}

We need to use the following code to register the current device time.

...
Future<void> _configureLocalTimeZone() async {
  tz.initializeTimeZones();
  final String? timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
  tz.setLocalLocation(tz.getLocation(timeZoneName!));
}
...

Also, we need to initialize iOS with the message permission request like the below. When iOS is initialized, not to show the permmission request alert, I set false to all options.

For Android, I set ic_notification to the app push icon. The icon should be copied to the ./android/app/src/main/res/drawable* folders.

...
Future<void> _initializeNotification() async {
  const DarwinInitializationSettings initializationSettingsIOS =
      DarwinInitializationSettings(
    requestAlertPermission: false,
    requestBadgePermission: false,
    requestSoundPermission: false,
  );
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('ic_notification');

  const InitializationSettings initializationSettings =
      InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: initializationSettingsIOS,
  );
  await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
}

Cancel registered messages

When a new message is registered, I just cancel all messages that are registered before by using the cancelAll() method.

...
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  ...
  Future<void> _cancelNotification() async {
    await _flutterLocalNotificationsPlugin.cancelAll();
  }
  ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Local Notifications'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            await _cancelNotification();
            ...
          },
          child: const Text('Show Notification'),
        ),
      ),
    );
  }
}

Request permission

Before a message is registered, we need to get a permission about sending message from the user on iOS. The following code doesn’t request again when the user decides the permission.

...
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  ...
  Future<void> _requestPermissions() async {
    await _flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            IOSFlutterLocalNotificationsPlugin>()
        ?.requestPermissions(
          alert: true,
          badge: true,
          sound: true,
        );
  }
  ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Local Notifications'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            ...
            await _requestPermissions();

            ...
          },
          child: const Text('Show Notification'),
        ),
      ),
    );
  }
}

Register message

Lastly, I register a message to be shown at one minute later than the current time. This message will be sent at the same time of every date.

...
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  ...
  Future<void> _registerMessage({
    required int hour,
    required int minutes,
    required message,
  }) async {
    final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
    tz.TZDateTime scheduledDate = tz.TZDateTime(
      tz.local,
      now.year,
      now.month,
      now.day,
      hour,
      minutes,
    );

    await _flutterLocalNotificationsPlugin.zonedSchedule(
      0,
      'flutter_local_notifications',
      message,
      scheduledDate,
      NotificationDetails(
        android: AndroidNotificationDetails(
          'channel id',
          'channel name',
          importance: Importance.max,
          priority: Priority.high,
          ongoing: true,
          styleInformation: BigTextStyleInformation(message),
          icon: 'ic_notification',
        ),
        iOS: const DarwinNotificationDetails(
          badgeNumber: 1,
        ),
      ),
      androidAllowWhileIdle: true,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      matchDateTimeComponents: DateTimeComponents.time,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Local Notifications'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            ...
            final tz.TZDateTime now = tz.TZDateTime.now(tz.local);
            await _registerMessage(
              hour: now.hour,
              minutes: now.minute + 1,
              message: 'Hello, world!',
            );
          },
          child: const Text('Show Notification'),
        ),
      ),
    );
  }
}

If you set the same ID on zonedSchedule, the duplicate message won’t be shown when the same ID message is shown currently.

...
await _flutterLocalNotificationsPlugin.zonedSchedule(
  0,
  ...
);
...

If you set true to ongoing on AndroidNotificationDetails, the message can be disappeared when the app is in the foreground.

await _flutterLocalNotificationsPlugin.zonedSchedule(
  ...
  NotificationDetails(
    android: AndroidNotificationDetails(
      ...
      ongoing: true,
      ...
    ),
    ...
  ),
  ...
);

Check

Let’s see flutter_local_notifications is working well. When you execute the simulater, you can see the screen like the below.

Flutter - flutter_local_notifications example

Now, when you press the Show Notification button, you can see the permission request dialog like the below.

Flutter - flutter_local_notifications permission alert

Press Allow in here to receive notifications. After then, make the app to be in Background and wait 1 minute. You can see the message like the below.

Flutter - flutter_local_notifications scheduled message

And then, when you start the app again, you can see the app icon badge is disappeared well.

Completed

Done! we’ve seen how to use flutter_local_notifications to show a messsage at the specific time from the user’s own device. Now, try this to develop an app to send notifications reqularly!

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

App promotion

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.

Posts