Using Firebase Cloud Messaging (FCM) as a Reliable Service for Sending Push Notifications in Flutter

In modern mobile application development, push notifications are a critical feature for engaging users and delivering timely information. Firebase Cloud Messaging (FCM) stands out as a robust and reliable solution for sending push notifications in Flutter applications. This guide provides a comprehensive overview of how to integrate and use FCM effectively in your Flutter projects.

What is Firebase Cloud Messaging (FCM)?

Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that enables you to reliably deliver messages and notifications at no cost. FCM provides a unified API that allows you to send messages to Android, iOS, and web applications.

Why Use FCM for Push Notifications in Flutter?

  • Reliability: FCM is known for its robust message delivery.
  • Cross-Platform: Supports Android, iOS, and web platforms.
  • Free of Charge: FCM is available at no cost.
  • Scalability: Can handle a large number of users and messages.
  • Integration with Firebase: Seamlessly integrates with other Firebase services.

Prerequisites

Before integrating FCM into your Flutter application, ensure you have the following:

  • A Firebase project set up in the Firebase Console.
  • A Flutter project created using Flutter SDK.
  • Properly configured Firebase for both Android and iOS platforms.

Step-by-Step Implementation of FCM in Flutter

Follow these steps to implement FCM in your Flutter application:

Step 1: Add Firebase to Your Flutter Project

Use the flutterfire configure command to link your Flutter project to your Firebase project. This command simplifies the configuration process.

flutterfire configure

Select the platforms (Android, iOS) you want to configure. This command automatically updates your Flutter project with the necessary Firebase configurations.

Step 2: Add Firebase Messaging Package

Add the firebase_messaging package to your pubspec.yaml file:

dependencies:
  firebase_core: ^2.15.0  # Check for the latest version
  firebase_messaging: ^14.6.0 # Check for the latest version

Run flutter pub get to install the packages.

Step 3: Initialize Firebase

In your main application file (e.g., main.dart), initialize Firebase:

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

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

Step 4: Request Notification Permissions

Request notification permissions from the user. This is essential for iOS and highly recommended for Android.

import 'package:firebase_messaging/firebase_messaging.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    requestPermissions();
    return MaterialApp(
      title: 'FCM Example',
      home: Scaffold(
        appBar: AppBar(
          title: Text('FCM Push Notifications'),
        ),
        body: Center(
          child: Text('Testing FCM integration'),
        ),
      ),
    );
  }

  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 permission: ${settings.authorizationStatus}');
  }
}

Step 5: Get the Device Token

Retrieve the FCM device token, which is used to send targeted notifications to a specific device.

import 'package:firebase_messaging/firebase_messaging.dart';

void getDeviceToken() async {
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  String? token = await messaging.getToken();

  print('Device Token: $token');
}

It’s a good practice to store this token in your backend server to send targeted notifications.

Step 6: Handle Incoming Notifications

Handle notifications that are received while the app is in the foreground, background, or terminated state.

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

// Initialize FlutterLocalNotificationsPlugin for handling foreground notifications
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");
}

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

  // Set up FlutterLocalNotificationsPlugin
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings('@mipmap/ic_launcher'); // Replace with your app's launcher icon
  final InitializationSettings initializationSettings =
      InitializationSettings(android: initializationSettingsAndroid);
  await flutterLocalNotificationsPlugin.initialize(initializationSettings);

  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

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

    // Foreground message handling
    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}');
        showNotification(message.notification!);
      }
    });

    // Initial message handling (when the app is opened from a terminated state)
    FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) {
      if (message != null) {
        print('Opened from terminated state: ${message.notification}');
      }
    });

    // Message handling when the app is in the background but not terminated
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('Message opened from background: ${message.notification}');
    });
  }

  // Function to show the notification
  void showNotification(RemoteNotification notification) async {
    const AndroidNotificationDetails androidPlatformChannelSpecifics =
        AndroidNotificationDetails(
      'your_channel_id', // Replace with your channel id
      'your_channel_name', // Replace with your channel name
      channelDescription: 'your channel description', // Replace with your channel description
      importance: Importance.max,
      priority: Priority.high,
      ticker: 'ticker',
    );
    const NotificationDetails platformChannelSpecifics =
        NotificationDetails(android: androidPlatformChannelSpecifics);
    await flutterLocalNotificationsPlugin.show(
      0, // Notification ID
      notification.title,
      notification.body,
      platformChannelSpecifics,
      payload: 'item x',
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FCM Example',
      home: Scaffold(
        appBar: AppBar(
          title: Text('FCM Push Notifications'),
        ),
        body: Center(
          child: Text('Testing FCM integration'),
        ),
      ),
    );
  }
}

Step 7: Send a Test Notification

You can send a test notification from the Firebase Console. Go to the Cloud Messaging section, and send a test message to the device token obtained earlier.

Advanced FCM Features

  • Topic Messaging: Send notifications to multiple devices that have subscribed to a particular topic.
  • Conditional Sending: Target notifications based on user segments, app versions, or custom conditions.
  • Data Messages: Send data messages to trigger specific actions within the application.

Using Topic Messaging

Subscribe devices to topics to send targeted notifications to groups of users.

FirebaseMessaging messaging = FirebaseMessaging.instance;
await messaging.subscribeToTopic('news');

Sending Data Messages

Send custom data messages to handle specific app functionalities.

final message = RemoteMessage(
  data: {
    'type': 'custom_action',
    'value': 'go_to_screen_x',
  },
);

Conclusion

Using Firebase Cloud Messaging (FCM) in Flutter provides a reliable and effective way to send push notifications. By following this comprehensive guide, you can successfully integrate FCM into your Flutter projects, enhance user engagement, and deliver timely updates and information. FCM’s flexibility and rich feature set make it an indispensable tool for any modern Flutter application that relies on push notifications.