Handling Different Types of Push Notifications (Data, Notification) in Flutter

Push notifications are a crucial component of modern mobile applications, enabling developers to engage users with timely updates and relevant information. In Flutter, handling different types of push notifications—specifically data and notification payloads—requires a nuanced approach to ensure seamless integration and optimal user experience. This blog post will delve into the intricacies of handling both data and notification push notifications in Flutter, offering practical examples and step-by-step guidance.

Understanding Push Notification Types

Push notifications come in two primary types, each with its own use case and behavior:

  • Notification Payload: These are designed to be displayed directly by the operating system (OS). When a notification payload arrives, the OS renders the notification in the system tray or notification center without requiring the app to be actively running in the foreground.
  • Data Payload: Data payloads require the app to handle the notification content programmatically. The app must be running in the foreground or background to process the data and display the notification (if desired).

Setting Up Firebase Cloud Messaging (FCM) in Flutter

To handle push notifications in Flutter, you’ll typically use Firebase Cloud Messaging (FCM), Google’s cross-platform messaging solution.

Step 1: Add Firebase to Your Flutter Project

First, you’ll need to set up a Firebase project and connect it to your Flutter application. Follow these steps:

  1. Create a Firebase Project: Go to the Firebase Console and create a new project.
  2. Register Your App: Add your Flutter app to the Firebase project by providing the necessary details (package name for Android, bundle ID for iOS).
  3. Download Configuration Files: Download the google-services.json (for Android) and GoogleService-Info.plist (for iOS) configuration files and add them to your Flutter project as instructed.
  4. Add Firebase Dependencies: Add the necessary Firebase dependencies to your pubspec.yaml file:

dependencies:
  firebase_core: ^2.15.0
  firebase_messaging: ^14.6.0

Run flutter pub get to install the dependencies.

Step 2: Initialize Firebase

In your main.dart file, initialize Firebase:


import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

Handling Notification Payloads

Notification payloads are straightforward to handle because the operating system displays them automatically. However, you might want to customize the behavior when a user taps on the notification.

Listening for Notification Taps

Use the FirebaseMessaging instance to listen for when a user taps on a notification:


import 'package:firebase_messaging/firebase_messaging.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  FirebaseMessaging messaging = FirebaseMessaging.instance;

  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    announcement: false,
    badge: true,
    carPlay: false,
    criticalAlert: false,
    provisional: false,
    sound: true,
  );

  print('User granted permission: ${settings.authorizationStatus}');

  FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    print('Got a message whilst in the foreground!');
    print('Message data: ${message.data}');

    if (message.notification != null) {
      print('Message also contained a notification: ${message.notification}');
    }
  });
  
  FirebaseMessaging.onMessageOpenedApp.listen((message) {
    print('Message opened app: ${message.notification}');
    // Handle navigation or other actions based on the notification.
  });

  runApp(MyApp());
}

The FirebaseMessaging.onMessageOpenedApp stream listens for when the user taps on a notification to open the app.

Handling Data Payloads

Handling data payloads requires more programmatic intervention since you are responsible for processing the data and, optionally, displaying a local notification.

Foreground Handling

When the app is in the foreground, use the onMessage stream to receive data payloads:


FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  print('Got a message whilst in the foreground!');
  print('Message data: ${message.data}');

  if (message.notification != null) {
    print('Message also contained a notification: ${message.notification}');
    // Display a local notification or perform other actions based on the data.
    showNotification(message.notification?.title ?? 'New Message', message.notification?.body ?? 'You have a new message');
  }
});

You’ll need to implement a showNotification function using a local notifications plugin, such as flutter_local_notifications:


import 'package:flutter_local_notifications/flutter_local_notifications.dart';

FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('app_icon'); // Replace with your app icon
  final InitializationSettings initializationSettings = InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: null, // iOSInitializationSettings are deprecated
      macOS: null); // macOSInitializationSettings are deprecated
  await flutterLocalNotificationsPlugin.initialize(initializationSettings);

  FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    print('Got a message whilst in the foreground!');
    print('Message data: ${message.data}');

    if (message.notification != null) {
      print('Message also contained a notification: ${message.notification}');
      // Display a local notification or perform other actions based on the data.
      showNotification(message.notification?.title ?? 'New Message', message.notification?.body ?? 'You have a new message');
    }
  });
  
  FirebaseMessaging.onMessageOpenedApp.listen((message) {
    print('Message opened app: ${message.notification}');
    // Handle navigation or other actions based on the notification.
  });

  runApp(MyApp());
}

Future showNotification(String title, String body) async {
  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
          'your_channel_id', 'your_channel_name',
          channelDescription: 'your_channel_description',
          importance: Importance.max,
          priority: Priority.high,
          ticker: 'ticker');
  const NotificationDetails platformChannelSpecifics =
      NotificationDetails(android: androidPlatformChannelSpecifics);
  await flutterLocalNotificationsPlugin.show(
       0, title, body, platformChannelSpecifics,
      payload: 'item x');
}

Note: Replace 'app_icon' with the name of your app icon resource.

Background Handling

To handle data payloads when the app is in the background or terminated, you need to use the setBackgroundMessageHandler:


@pragma('vm:entry-point')
Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");
  // Process the data and display a notification if needed.
  showNotification(message.notification?.title ?? 'New Message', message.notification?.body ?? 'You have a new message');
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  runApp(MyApp());
}

Note: The setBackgroundMessageHandler must be a top-level function or a static method.

Sending Push Notifications

To send push notifications, you can use the Firebase Console or the FCM HTTP API.

Sending via Firebase Console

  1. Go to the Firebase Console, select your project, and navigate to “Cloud Messaging.”
  2. Click on “Send your first message.”
  3. Compose your message, specifying the notification title, body, and optionally, data payload.
  4. Send the message to your target audience.

Sending via FCM HTTP API

To send notifications programmatically, you can use the FCM HTTP API. Here’s an example of sending a data payload using a simple HTTP request:


{
  "to": "YOUR_DEVICE_TOKEN",
  "data": {
    "title": "New Message",
    "body": "You have a new message from John."
  },
  "notification":{
     "title": "Notification Title",
     "body": "Notification Body"
  }
}

Send this JSON payload to the FCM API endpoint with the appropriate headers (including your Firebase project’s server key).

Best Practices for Handling Push Notifications

  • Handle Errors: Implement proper error handling to catch and log any issues with push notification delivery.
  • Test Thoroughly: Test your push notification implementation on both Android and iOS devices to ensure consistent behavior.
  • Optimize for Battery Life: Avoid excessive use of push notifications, as they can drain the device’s battery.
  • Respect User Preferences: Provide users with granular control over the types of push notifications they receive.
  • Secure Sensitive Data: Never include sensitive information directly in push notification payloads.

Conclusion

Handling different types of push notifications in Flutter involves careful setup and nuanced handling of notification and data payloads. By leveraging Firebase Cloud Messaging and following the guidelines outlined in this post, developers can create engaging and responsive mobile applications that keep users informed and connected. Proper implementation ensures that both notification and data messages are delivered and processed efficiently, enhancing the overall user experience.