Using Firebase Cloud Messaging (FCM) to Send and Receive Push Notifications to Users in Flutter

Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably send messages and notifications at no cost. In Flutter, FCM is widely used to implement push notifications, allowing you to engage users even when they’re not actively using your app. This blog post provides a comprehensive guide on integrating FCM into your Flutter app to send and receive push notifications.

What is Firebase Cloud Messaging (FCM)?

Firebase Cloud Messaging (FCM) is a service that enables you to send notifications and data from servers to mobile devices and vice versa. FCM is built for scale and provides a unified way to send push notifications across various platforms such as Android, iOS, and web apps. With FCM, you can send two types of messages: notification messages and data messages.

Why Use FCM in Flutter?

  • User Engagement: Keeps users informed and engaged with timely notifications.
  • Cross-Platform: Supports sending messages to Android, iOS, and web apps from a single platform.
  • Reliable Delivery: FCM ensures reliable message delivery even when the app is in the background or terminated.
  • Cost-Effective: FCM is free of charge, making it an ideal choice for sending push notifications.

How to Integrate FCM in a Flutter App

Follow these steps to integrate FCM into your Flutter app:

Step 1: Set Up Firebase Project

First, you need to create a Firebase project in the Firebase Console.

  1. Go to the Firebase Console.
  2. Click on “Add project.”
  3. Enter your project name, and follow the prompts to configure your project.

Step 2: Add Firebase to Your Flutter App

2.1: Android Setup
  1. Register your Android app by adding the app’s package name to your Firebase project.
  2. Download the google-services.json file and place it in the android/app/ directory of your Flutter project.
  3. Add the Firebase SDK to your project. In your project-level build.gradle (android/build.gradle), add the following dependency:
dependencies {
    classpath "com.google.gms:google-services:4.3.15" // Make sure to use the latest version
}
  1. In your app-level build.gradle (android/app/build.gradle), add the following plugins and dependencies:
plugins {
    id "com.android.application"
    id "org.jetbrains.kotlin.android"
    id "com.google.gms.google-services"
}

dependencies {
    implementation platform('com.google.firebase:firebase-bom:32.7.2')  // Use the latest version

    implementation 'com.google.firebase:firebase-analytics-ktx'
    implementation 'com.google.firebase:firebase-messaging-ktx'
}
  1. Sync your Gradle files.
2.2: iOS Setup
  1. Register your iOS app by adding the app’s bundle ID to your Firebase project.
  2. Download the GoogleService-Info.plist file and add it to the root of your Xcode project.
  3. Add the Firebase SDK to your project using CocoaPods. Create or open the Podfile in the ios/ directory of your Flutter project and add the following lines:
platform :ios, '11.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

target 'Runner' do
  use_frameworks!
  pods_for_target 'Runner'

  pod 'FirebaseCore'
  pod 'FirebaseMessaging'
end
  1. Run pod install in the ios/ directory.
  2. In your Xcode project, enable push notifications:
    • Go to your project settings.
    • Select “Signing & Capabilities.”
    • Add the “Push Notifications” capability.
  3. Configure background modes:
    • Select “Signing & Capabilities.”
    • Add the “Background Modes” capability and check “Remote notifications.”

Step 3: Add the Firebase Messaging Plugin

In your Flutter project, add the firebase_messaging plugin to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^2.24.2
  firebase_messaging: ^14.9.0

Run flutter pub get to install the plugin.

Step 4: Initialize Firebase

In your main.dart file, initialize Firebase before using any Firebase services:

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

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

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

Step 5: Request Notification Permissions

To receive notifications on iOS, you need to request permissions from the user:

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}');

  runApp(MyApp());
}

Step 6: Get the Device Token

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

import 'package:firebase_messaging/firebase_messaging.dart';

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

  FirebaseMessaging messaging = FirebaseMessaging.instance;

  String? token = await messaging.getToken();
  print('Firebase token: $token');

  runApp(MyApp());
}

Save this token to your server or use it to send test notifications.

Step 7: Handle Incoming Messages

You can handle incoming messages in three scenarios: foreground, background, and terminated state.

7.1: Foreground State

To handle messages when the app is in the foreground, use FirebaseMessaging.onMessage:

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

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

  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}');
    }
  });

  runApp(MyApp());
}
7.2: Background State

To handle messages when the app is in the background or terminated, use FirebaseMessaging.onBackgroundMessage. This handler must be a top-level function:

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

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

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

  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  runApp(MyApp());
}
7.3: Terminated State

You can also handle the initial message when the app is launched from a terminated state:

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

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

  RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();

  if (initialMessage != null) {
    // Handle the message here
    print('Initial message: ${initialMessage.notification}');
  }

  runApp(MyApp());
}

Step 8: Sending a Test Notification

You can send test notifications via the Firebase Console:

  1. Go to the Firebase Console.
  2. Select your project.
  3. Navigate to “Cloud Messaging” under the “Engage” section.
  4. Click on “Send your first message.”
  5. Fill in the notification details, such as title and body.
  6. In the “Send message” section, choose “Send to device” and enter your device token.
  7. Click “Send.”

Complete Example

Here’s a complete example combining all the steps:

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

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

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

  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

  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}');

  String? token = await messaging.getToken();
  print('Firebase token: $token');

  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}');
    }
  });

  RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();

  if (initialMessage != null) {
    print('Initial message: ${initialMessage.notification}');
  }

  runApp(MyApp());
}

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

Conclusion

Integrating Firebase Cloud Messaging (FCM) into your Flutter app enhances user engagement and ensures timely delivery of important notifications. By following the steps outlined in this blog post, you can successfully set up FCM, handle incoming messages in various app states, and send test notifications. Implementing FCM in your Flutter app enables you to keep your users informed and connected, thereby improving the overall app experience.