Implementing Push Notifications in Flutter

Push notifications are a vital part of modern mobile applications, enabling developers to engage users with timely and relevant information. In Flutter, implementing push notifications involves setting up Firebase Cloud Messaging (FCM) and handling notifications appropriately on both Android and iOS platforms. This article will guide you through the process of implementing push notifications in a Flutter app, including configuration and best practices.

What are Push Notifications?

Push notifications are messages that appear on a user’s device outside of the application. They can be used to deliver updates, reminders, promotional offers, or any other kind of timely information. These notifications are ‘pushed’ from a server to the device, regardless of whether the user is currently using the app.

Why Use Push Notifications?

  • User Engagement: Re-engage users who might not have used the app recently.
  • Timely Updates: Provide immediate information, such as news updates or reminders.
  • Marketing: Deliver promotional offers or announce new features.
  • Improved User Experience: Offer helpful notifications to improve the user’s overall experience.

Implementing Push Notifications in Flutter: Step-by-Step Guide

Here’s a detailed guide on how to implement push notifications in a Flutter application:

Step 1: Set up Firebase Project

First, you’ll need to create a new project in Firebase or use an existing one:

  1. Go to the Firebase Console.
  2. Click “Add project” and follow the steps to create a new project.

Step 2: Add Firebase to Your Flutter App

  1. Register your apps: In the Firebase console, add both your Android and iOS apps to the project.
  2. Download configuration files:
    • For Android, download google-services.json and place it in android/app/ directory.
    • For iOS, download GoogleService-Info.plist and add it to your Xcode project.

Step 3: Configure Android

  1. Add Firebase SDK: In your android/build.gradle, add the following:
  2. dependencies {
        classpath("com.google.gms:google-services:4.4.0")  // Check for the latest version
    }
  3. In your android/app/build.gradle, apply the plugin and add Firebase dependencies:
  4. apply plugin: 'com.google.gms.google-services'
    
    dependencies {
        implementation platform('com.google.firebase:firebase-bom:32.8.0')  // Use the latest BOM
        implementation 'com.google.firebase:firebase-messaging-ktx'
    }

Step 4: Configure iOS

  1. Add Firebase SDK: Use CocoaPods. In your Podfile:
  2. pod 'Firebase/Messaging'
  3. Run pod install.
  4. Enable Push Notifications: In Xcode, go to your target settings, select “Signing & Capabilities”, and add the “Push Notifications” capability. Also, add the “Background Modes” capability and enable “Remote notifications”.

Step 5: Add Flutter Firebase Messaging Package

Add the firebase_messaging package to your pubspec.yaml file:

dependencies:
  firebase_core: ^2.24.2
  firebase_messaging: ^14.9.10

Then run flutter pub get.

Step 6: Initialize Firebase in Flutter

Initialize Firebase in your main function:

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

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

Step 7: Request Permission for Notifications

Request permission from the user to send notifications:

import 'package:firebase_messaging/firebase_messaging.dart';

Future requestNotificationPermissions() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;

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

  if (settings.authorizationStatus == AuthorizationStatus.authorized) {
    print('User granted permission');
  } else if (settings.authorizationStatus == AuthorizationStatus.provisional) {
    print('User granted provisional permission');
  } else {
    print('User declined or has not accepted permission');
  }
}

Step 8: Get the Device Token

Get the device token to send targeted notifications:

Future getDeviceToken() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  return await messaging.getToken();
}

Step 9: Handle Incoming Notifications

Handle incoming notifications using listeners:

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

Future handleFirebaseMessaging() async {
  // Handle background messages
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  // Handle foreground messages
  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}');
      displayNotification(message);
    }
  });

  // Handle when app is opened from notification
  FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
    print('Opened app from notification: ${message.notification}');
    // Navigate to specific screen based on the notification
  });
}

@pragma('vm:entry-point')
Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");
}

Future displayNotification(RemoteMessage message) async {
  //Show notification using flutter_local_notifications plugin
  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'high_importance_channel', // id
    'High Importance Notifications', // title
    description: 'This channel is used for important notifications.', // description
    importance: Importance.max,
  );

  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<
      AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  const AndroidNotificationDetails androidPlatformChannelSpecifics =
  AndroidNotificationDetails(
      'high_importance_channel',
      'High Importance Notifications',
      channelDescription: 'Channel description',
      importance: Importance.max,
      priority: Priority.high,
      ticker: 'ticker');

  const NotificationDetails platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics);

  await flutterLocalNotificationsPlugin.show(
      0,
      message.notification?.title,
      message.notification?.body,
      platformChannelSpecifics,
      payload: 'item x');

}

Step 10: Display Notifications using flutter_local_notifications

Add the flutter_local_notifications package to your pubspec.yaml file:

dependencies:
  flutter_local_notifications: ^16.3.2

And then implement notification display as shown above in Step 9. Ensure correct initialization:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

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

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

Future initializeLocalNotifications() async {
  const AndroidInitializationSettings initializationSettingsAndroid =
  AndroidInitializationSettings('@mipmap/ic_launcher');

  const DarwinInitializationSettings initializationSettingsIOS =
  DarwinInitializationSettings(
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
  );

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

  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) async {
        // Handle when a user taps on a notification
      }
  );
}

Best Practices

  • Handle all notification states: Implement handlers for background, foreground, and terminated app states.
  • Use specific channels for different notifications: Android requires the use of notification channels to categorize different types of notifications.
  • Secure the Firebase Server Key: Store the server key securely and avoid exposing it in your client-side code.
  • Test notifications on both Android and iOS: Ensure the notification behavior is consistent across platforms.

Conclusion

Implementing push notifications in a Flutter application involves several steps, from setting up a Firebase project to handling incoming messages and displaying them appropriately. By following the guidelines and code examples in this article, you can create an engaging notification system that enhances the user experience and keeps users connected to your app.