Implementing Local Notifications Using Packages Like flutter_local_notifications for Reminders and Alerts in Flutter

Local notifications are a great way to engage users with timely and relevant information without requiring an internet connection. In Flutter, you can implement local notifications easily using packages like flutter_local_notifications. This article provides a comprehensive guide to implementing local notifications for reminders and alerts in Flutter.

What are Local Notifications?

Local notifications are messages that pop up on a user’s device without needing a server or internet connection. These notifications can be scheduled to appear at specific times, making them ideal for reminders, alerts, and other time-sensitive information.

Why Use Local Notifications?

  • Engage Users: Provide timely reminders and alerts.
  • Offline Functionality: Work without an internet connection.
  • Improved User Experience: Deliver relevant information at the right time.

Prerequisites

Before you start, make sure you have Flutter installed and set up correctly. You also need to add the flutter_local_notifications package to your pubspec.yaml file.

Step 1: Add Dependencies

Add the flutter_local_notifications and rxdart packages to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_local_notifications: ^16.3.2
  rxdart: ^0.27.5

Then, run flutter pub get to install the dependencies.

Step 2: Set Up FlutterLocalNotificationsPlugin

In your main.dart file, initialize the FlutterLocalNotificationsPlugin:

import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:rxdart/rxdart.dart';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

final BehaviorSubject<ReceivedNotification> didReceiveLocalNotificationSubject =
    BehaviorSubject<ReceivedNotification>();

final BehaviorSubject<String?> selectNotificationSubject =
    BehaviorSubject<String?>();

class ReceivedNotification {
  ReceivedNotification({
    required this.id,
    required this.title,
    required this.body,
    required this.payload,
  });

  final int id;
  final String? title;
  final String? body;
  final String? payload;
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('app_icon');

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

  await flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) {
      selectNotificationSubject.add(notificationResponse.payload);
    },
    onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
  );

  runApp(MyApp());
}

@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
    // handle action
    print('notification(${notificationResponse.id}) action tapped: '
        '${notificationResponse.actionId} with'
        ' payload: ${notificationResponse.payload}');
    if (notificationResponse.input?.isNotEmpty ?? false) {
      print(
          ' notification action tapped with input: ${notificationResponse.input}');
    }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    selectNotificationSubject.stream.listen((String? payload) {
      if (payload != null) {
        print('Notification payload: $payload');
      }
    });
  }

  @override
  void dispose() {
    didReceiveLocalNotificationSubject.close();
    selectNotificationSubject.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Local Notifications Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {
                _showNotification();
              },
              child: Text('Show Notification'),
            ),
            ElevatedButton(
              onPressed: () {
                _scheduleNotification();
              },
              child: Text('Schedule Notification'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _showNotification() async {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
            'channelId', 
            'channelName',
            channelDescription: 'channelDescription',
            importance: Importance.max,
            priority: Priority.high,
            ticker: 'ticker');

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
    );

    await flutterLocalNotificationsPlugin.show(
      0,
      'Test Title',
      'Test Body',
      notificationDetails,
      payload: 'item x',
    );
  }

  Future<void> _scheduleNotification() async {
    final DateTime now = DateTime.now();
    final DateTime scheduledDate = now.add(Duration(seconds: 5));

    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
      'scheduledChannelId',
      'scheduledChannelName',
      channelDescription: 'scheduledChannelDescription',
      importance: Importance.max,
      priority: Priority.high,
      ticker: 'ticker',
    );

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
    );

    await flutterLocalNotificationsPlugin.schedule(
      1,
      'Scheduled Title',
      'Scheduled Body',
      scheduledDate,
      notificationDetails,
      payload: 'item y',
      androidAllowWhileIdle: true,
    );
  }
}

Replace 'app_icon' with the name of your app icon (without extension) that you’ve placed in the android/app/src/main/res/drawable folder.

Step 3: Display a Simple Notification

Add the following method to the _MyHomePageState class to display a simple notification:

Future<void> _showNotification() async {
  const AndroidNotificationDetails androidNotificationDetails =
      AndroidNotificationDetails(
          'your channel id', 'your channel name',
          channelDescription: 'your channel description',
          importance: Importance.max,
          priority: Priority.high,
          ticker: 'ticker');
  const NotificationDetails notificationDetails = NotificationDetails(
    android: androidNotificationDetails,
  );
  await flutterLocalNotificationsPlugin.show(
    0,
    'Plain Title',
    'Plain Body',
    notificationDetails,
    payload: 'item x',
  );
}

Now, call this method from a button to show the notification:

ElevatedButton(
  onPressed: () {
    _showNotification();
  },
  child: Text('Show Notification'),
),

Step 4: Schedule a Notification

To schedule a notification, add the following method to the _MyHomePageState class:

Future<void> _scheduleNotification() async {
  final DateTime now = DateTime.now();
  final DateTime scheduledDate = now.add(Duration(seconds: 5));

  const AndroidNotificationDetails androidNotificationDetails =
      AndroidNotificationDetails(
    'channelId',
    'Channel Name',
    channelDescription: 'Channel Description',
    importance: Importance.max,
    priority: Priority.high,
    ticker: 'ticker',
  );

  const NotificationDetails notificationDetails = NotificationDetails(
    android: androidNotificationDetails,
  );

  await flutterLocalNotificationsPlugin.schedule(
    1,
    'Scheduled Title',
    'Scheduled Body',
    scheduledDate,
    notificationDetails,
    payload: 'item y',
    androidAllowWhileIdle: true,
  );
}

Add a button to trigger the scheduled notification:

ElevatedButton(
  onPressed: () {
    _scheduleNotification();
  },
  child: Text('Schedule Notification'),
),

This will schedule a notification to appear 5 seconds after the button is pressed.

Handling Notification Interactions

To handle when a user taps on a notification, you can use the selectNotificationSubject. Add the following code in your initState method:

@override
void initState() {
  super.initState();
  selectNotificationSubject.stream.listen((String? payload) {
    if (payload != null) {
      print('Notification payload: $payload');
      // Add navigation logic here
    }
  });
}

You can navigate to a specific screen or perform any other action based on the payload.

Customizing Notifications

The flutter_local_notifications package provides many options for customizing your notifications. Here are a few examples:

Setting Notification Icons

To set a custom icon for your notification, make sure the icon file (e.g., app_icon.png) is placed in the android/app/src/main/res/drawable folder. Then, reference it in the AndroidInitializationSettings:

const AndroidInitializationSettings initializationSettingsAndroid =
    AndroidInitializationSettings('app_icon');

Adding Actions to Notifications

You can add actions to your notifications, such as buttons to perform specific tasks:

const AndroidNotificationDetails androidNotificationDetails =
    AndroidNotificationDetails(
  'channelId',
  'Channel Name',
  channelDescription: 'Channel Description',
  importance: Importance.max,
  priority: Priority.high,
  ticker: 'ticker',
  actions: <AndroidNotificationAction>[
    AndroidNotificationAction('id_1', 'Action 1'),
    AndroidNotificationAction('id_2', 'Action 2'),
  ],
);

Error Handling and Troubleshooting

If you encounter any issues with local notifications, check the following:

  • Permissions: Ensure the necessary permissions are granted (especially on Android 13 and above).
  • Initialization: Make sure the FlutterLocalNotificationsPlugin is initialized correctly.
  • Channel IDs: Use unique and descriptive channel IDs.
  • Android Icon: Verify that the app icon is correctly placed and referenced.

Complete Example

Here is the complete code for implementing local notifications in Flutter:


import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:rxdart/rxdart.dart';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

final BehaviorSubject<ReceivedNotification> didReceiveLocalNotificationSubject =
    BehaviorSubject<ReceivedNotification>();

final BehaviorSubject<String?> selectNotificationSubject =
    BehaviorSubject<String?>();

class ReceivedNotification {
  ReceivedNotification({
    required this.id,
    required this.title,
    required this.body,
    required this.payload,
  });

  final int id;
  final String? title;
  final String? body;
  final String? payload;
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('app_icon');

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

  await flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
    onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) {
      selectNotificationSubject.add(notificationResponse.payload);
    },
    onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
  );

  runApp(MyApp());
}

@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
    // handle action
    print('notification(${notificationResponse.id}) action tapped: '
        '${notificationResponse.actionId} with'
        ' payload: ${notificationResponse.payload}');
    if (notificationResponse.input?.isNotEmpty ?? false) {
      print(
          ' notification action tapped with input: ${notificationResponse.input}');
    }
}


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    selectNotificationSubject.stream.listen((String? payload) {
      if (payload != null) {
        print('Notification payload: $payload');
        // Add navigation logic here
      }
    });
  }

  @override
  void dispose() {
    didReceiveLocalNotificationSubject.close();
    selectNotificationSubject.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Local Notifications Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () {
                _showNotification();
              },
              child: Text('Show Notification'),
            ),
            ElevatedButton(
              onPressed: () {
                _scheduleNotification();
              },
              child: Text('Schedule Notification'),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _showNotification() async {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
            'channelId', 
            'channelName',
            channelDescription: 'channelDescription',
            importance: Importance.max,
            priority: Priority.high,
            ticker: 'ticker');

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
    );

    await flutterLocalNotificationsPlugin.show(
      0,
      'Test Title',
      'Test Body',
      notificationDetails,
      payload: 'item x',
    );
  }

  Future<void> _scheduleNotification() async {
    final DateTime now = DateTime.now();
    final DateTime scheduledDate = now.add(Duration(seconds: 5));

    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
      'scheduledChannelId',
      'scheduledChannelName',
      channelDescription: 'scheduledChannelDescription',
      importance: Importance.max,
      priority: Priority.high,
      ticker: 'ticker',
    );

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
    );

    await flutterLocalNotificationsPlugin.schedule(
      1,
      'Scheduled Title',
      'Scheduled Body',
      scheduledDate,
      notificationDetails,
      payload: 'item y',
      androidAllowWhileIdle: true,
    );
  }
}

Conclusion

Local notifications are an effective way to enhance user engagement in Flutter applications. By using the flutter_local_notifications package, you can easily implement and customize notifications for various purposes, such as reminders and alerts. Make sure to handle notification interactions properly to provide a seamless user experience. By following this guide, you can create compelling and useful local notifications in your Flutter apps.