Using Firebase Cloud Messaging (FCM) for Sending Push Notifications in Flutter

Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that enables you to reliably send messages at no cost. Using FCM, you can notify a client app that new email or other data are available to sync. You can send notifications to drive user engagement and retention, send transactional messages, and more. In Flutter, integrating FCM is crucial for building engaging, real-time applications.

What is Firebase Cloud Messaging (FCM)?

Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connection between your server and devices, enabling you to deliver push notifications. FCM handles the complexity of managing multiple messaging technologies, such as upstream messaging for sending data from the app to the server.

Why Use FCM in Flutter?

  • Cross-Platform Support: Supports both Android and iOS devices with the same codebase.
  • Reliability: Ensures high delivery rates with FCM’s optimized network.
  • Cost-Effective: Free to use up to a generous limit.
  • Flexibility: Allows for various message types including notification messages, data messages, and more.

How to Implement FCM in a Flutter Application

To implement FCM in your Flutter application, you’ll need to perform several steps. This includes setting up Firebase, adding dependencies to your Flutter project, configuring platform-specific settings, and handling incoming messages.

Step 1: Set Up a Firebase Project

First, you need to create a Firebase project.

  1. Go to the Firebase Console and sign in with your Google account.
  2. Click on “Add project” and follow the instructions to create a new project.
  3. Once the project is created, navigate to the Project settings.
  4. Add both Android and iOS apps to your Firebase project.

Step 2: Add Firebase to Your Flutter Project

Next, you need to add Firebase to your Flutter project.

Add Firebase Core
dependencies:
  firebase_core: ^2.24.2

Run: flutter pub add firebase_core

Add Firebase Messaging
dependencies:
  firebase_messaging: ^14.9.3

Run: flutter pub add firebase_messaging

Step 3: Configure Firebase for Android

Configure Firebase for Android by downloading the google-services.json file from your Firebase project and placing it in the android/app directory.

  1. In your android/build.gradle file, add the Google Services plugin:
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.4.0'
    }
}
  1. In your android/app/build.gradle file, apply the Google Services plugin:
apply plugin: 'com.google.gms.google-services'

Step 4: Configure Firebase for iOS

Configure Firebase for iOS by downloading the GoogleService-Info.plist file from your Firebase project and adding it to your Xcode project. Open your project in Xcode:

  1. Right-click on the project navigator and select “Add Files to ‘Runner’.”
  2. Select the GoogleService-Info.plist file and add it to your project.

Step 5: Initialize Firebase in Your Flutter App

Initialize Firebase in your Flutter app in the 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 6: Requesting Notification Permissions

On iOS, and some newer versions of Android, you need to explicitly request permission to send notifications.

import 'package:firebase_messaging/firebase_messaging.dart';

Future<void> requestPermissions() async {
  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 permissions: ${settings.authorizationStatus}');
}

Step 7: Get the FCM Token

To send notifications to a specific device, you need to obtain the FCM token.

import 'package:firebase_messaging/firebase_messaging.dart';

Future<String?> getToken() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  String? token = await messaging.getToken();
  print('FCM Token: $token');
  return token;
}

Step 8: Handling Incoming Messages

To handle incoming messages, set up a listener for foreground messages and background messages.

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; // Import the plugin

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();


Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // Initialize Firebase
  await Firebase.initializeApp();

  // Set up Android notification details
  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
          'your_channel_id', 'your_channel_name',
          channelDescription: 'Your channel description',
          importance: Importance.max,
          priority: Priority.high,
          ticker: 'ticker');

  // Set up iOS notification details
  const DarwinNotificationDetails iOSPlatformChannelSpecifics =
      DarwinNotificationDetails(presentAlert: true, presentBadge: true, presentSound: true);

  // Combine notification details for both platforms
  const NotificationDetails platformChannelSpecifics = NotificationDetails(
      android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics);

  // Show the notification
  await flutterLocalNotificationsPlugin.show(
    0, // Notification ID
    message.notification?.title, // Notification Title
    message.notification?.body, // Notification Body
    platformChannelSpecifics, // Notification Details
    payload: 'item x', // You can add additional data here if needed
  );

  print("Handling a background message: ${message.messageId}");
}

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

  // Set up notification settings for Android
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('app_icon'); // replace with your icon

  // Set up notification settings for iOS
  const DarwinInitializationSettings initializationSettingsIOS =
      DarwinInitializationSettings(
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
  );

  // Combine notification settings for both platforms
  const InitializationSettings initializationSettings = InitializationSettings(
    android: initializationSettingsAndroid,
    iOS: initializationSettingsIOS,
  );

  // Initialize the flutter_local_notifications plugin
  await flutterLocalNotificationsPlugin.initialize(
    initializationSettings,
  );


  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firebase Messaging Example',
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State {
  @override
  void initState() {
    super.initState();

    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      RemoteNotification? notification = message.notification;
      AndroidNotification? android = message.notification?.android;

      // If `onMessage` is triggered when the app is active and has a notification, show a local notification
      if (notification != null && android != null) {
        flutterLocalNotificationsPlugin.show(
          notification.hashCode,
          notification.title,
          notification.body,
          NotificationDetails(
            android: AndroidNotificationDetails(
              'your_channel_id',
              'Your Channel Name',
              channelDescription: 'Your Channel Description',
              icon: 'app_icon', // Replace with your app icon
            ),
          ),
        );
      }
    });

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('A new onMessageOpenedApp event was published!');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Messaging Example'),
      ),
      body: Center(
        child: Text('Check for notifications!'),
      ),
    );
  }
}

Step 9: Sending Test Messages

You can send test messages from the Firebase console or using the Firebase Admin SDK.

  1. Go to the Firebase Console.
  2. Select “Cloud Messaging” in the left navigation panel.
  3. Click “Send your first message.”
  4. Enter the notification title, text, and target the message to a specific FCM token.
  5. Send the test message and check if it arrives on your device.

Advanced Features

  • Topic Messaging: Sending messages to devices that have subscribed to a particular topic.
  • Conditional Sending: Using conditions to target messages to specific devices based on attributes like app version or device language.
  • Custom Data: Including custom data in messages to perform specific actions on the receiving device.

Conclusion

Integrating Firebase Cloud Messaging (FCM) into your Flutter application enables you to effectively send push notifications, enhance user engagement, and deliver real-time updates. By following the detailed steps outlined in this guide, you can seamlessly configure Firebase, handle incoming messages, and leverage advanced FCM features to create a robust and interactive user experience.