Using Firebase Cloud Messaging for Push Notifications in Flutter

In the world of mobile app development, push notifications are essential for engaging users and keeping them informed about important updates, messages, or events. Firebase Cloud Messaging (FCM) provides a reliable and scalable solution for implementing push notifications in Flutter apps. This comprehensive guide will walk you through setting up FCM and integrating it into your Flutter project.

What is Firebase Cloud Messaging (FCM)?

Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably deliver messages and notifications at no cost. FCM allows you to send notifications, transfer messages, and perform various types of mobile communications between your server and client apps.

Why Use FCM for Push Notifications?

  • Reliability: FCM provides reliable message delivery, ensuring notifications reach your users.
  • Scalability: It’s designed to scale to millions of users without compromising performance.
  • Cost-Effective: FCM is available at no cost, making it a great choice for projects of all sizes.
  • Cross-Platform Support: FCM supports Android, iOS, and web platforms.
  • Rich Features: Offers features like message targeting, analytics, and advanced options for customizing notification behavior.

Prerequisites

Before you begin, make sure you have the following:

  • A Flutter development environment set up (Flutter SDK, IDE like VS Code or Android Studio).
  • A Firebase project created on the Firebase Console (console.firebase.google.com).
  • A basic understanding of Dart and Flutter.

Step-by-Step Implementation

Step 1: Set Up Firebase Project

First, you need to create a new project in the Firebase Console or use an existing one. Follow these steps:

  1. Go to the Firebase Console.
  2. Click “Add project” and follow the instructions.
  3. Once your project is created, navigate to “Project settings.”

Step 2: Add Flutter App to Firebase Project

Now, add your Flutter app to the Firebase project. Follow these steps:

For Android:
  1. In the Firebase Console, click the Android icon.
  2. Enter your app’s package name (found in android/app/build.gradle under defaultConfig).
  3. Download the google-services.json file and place it in your android/app/ directory.
  4. Add the Firebase SDK to your project:
    • In your project-level build.gradle file (android/build.gradle):
      
      dependencies {
          classpath("com.google.gms:google-services:4.4.0")  // check for latest version
      }
                      
    • In your app-level build.gradle file (android/app/build.gradle):
      
      apply plugin: 'com.google.gms.google-services'
      
      dependencies {
          implementation platform('com.google.firebase:firebase-bom:32.8.0') // Check for the latest version
          implementation 'com.google.firebase:firebase-analytics-ktx'
          implementation 'com.google.firebase:firebase-messaging-ktx'
      }
                      
  5. Sync your Gradle files to apply the changes.
For iOS:
  1. In the Firebase Console, click the iOS icon.
  2. Enter your app’s Bundle ID (found in Xcode under your project target’s General tab).
  3. Download the GoogleService-Info.plist file and add it to your Xcode project.
  4. Add the Firebase SDK to your project via CocoaPods.
    • Create or update your Podfile:
      
      platform :ios, '11.0' # ensure min ios version is configured
      
      target 'Runner' do
        use_frameworks!
        pod 'Firebase/Core'
        pod 'Firebase/Messaging'
      end
                      
    • Run pod install in your terminal from the ios directory.
  5. Open your Xcode project’s ios/Runner/AppDelegate.swift (or AppDelegate.m) and add the following:

    Swift:

    
    import FirebaseCore
    import FirebaseMessaging
    
    @UIApplicationMain
    class AppDelegate: FlutterAppDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        FirebaseApp.configure()
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }
        

    Objective-C:

    
    @import Firebase;
    
    - (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      [FIRApp configure];
      return [super application:application didFinishLaunchingWithOptions:launchOptions];
    }
        
  6. For Receiving Notifications you need to enable the Push Notifications capability on your Xcode project. Also ensure that you have correctly configured your APNs certificates in the Apple Developer Portal and uploaded to Firebase Cloud Messaging settings page.

Step 3: Add Firebase Messaging Plugin to Flutter

Add the firebase_messaging plugin to your Flutter project. This plugin allows your Flutter app to receive and handle FCM messages.


dependencies:
  firebase_core: ^2.31.0 # Check latest version
  firebase_messaging: ^14.6.0 # Check latest version

Run flutter pub get in your terminal to install the plugin.

Step 4: Initialize Firebase in Flutter

Initialize Firebase in your Flutter app’s 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 5: Request Notification Permissions

Request permission from the user to send notifications:


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

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

  void requestPermission() 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: ${settings.authorizationStatus}');
    } else if (settings.authorizationStatus == AuthorizationStatus.provisional) {
      print('User granted provisional permission');
    } else {
      print('User declined or has not accepted permission');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter FCM Demo',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FCM Example'),
        ),
        body: Center(
          child: Text('Welcome to Flutter FCM'),
        ),
      ),
    );
  }
}

Step 6: Get the FCM Token

Obtain the FCM token for the device, which you’ll need to send targeted notifications. Store this token on your server:


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

class _MyAppState extends State {
  @override
  void initState() {
    super.initState();
    getToken();
    requestPermission();
  }
  String? token;

  void getToken() async {
    await FirebaseMessaging.instance.getToken().then((value){
      setState(() {
        token = value;
      });
      print('FCM Token: $token'); // Log the token or send to server
    });

  }

  void requestPermission() async {
   //Permission logic ...
  }

  @override
  Widget build(BuildContext context) {
   //Build widget  
  }
}

Step 7: Handle Incoming Messages

Implement message handling for when the app is in the foreground, background, or terminated:

Foreground Messages:

  @override
  void initState() {
    super.initState();
    getToken();
    requestPermission();

    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Handling a foreground message: ${message.messageId}');
      print('Message data: ${message.data}');

      if (message.notification != null) {
        print('Message also contained a notification: ${message.notification?.body}');
      }

      // Handle the message (e.g., display a dialog, update UI)
    });
  }
Background and Terminated Messages:

Define a top-level function to handle background messages. This function must be outside any class. For isolate support, Firebase requires this as a top-level function


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

Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");
  print('Message data: ${message.data}');
  // Handle background message (e.g., show notification, update local data)
}

Then assign this to the onBackgroundMessage


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

  runApp(MyApp());
}

Step 8: Send a Test Notification

You can send test notifications from the Firebase Console:

  1. Go to the Firebase Console.
  2. Select “Cloud Messaging.”
  3. Click “Send your first message.”
  4. Fill in the required fields (notification title, text, etc.).
  5. Send the notification to the device using the FCM token.

Code Example: Full Integration

Here’s the complete example of implementing Firebase Cloud Messaging in a Flutter app:


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}");
  print('Message data: ${message.data}');
}

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

  runApp(MyApp());
}

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

class _MyAppState extends State {
  String? token;

  @override
  void initState() {
    super.initState();
    getToken();
    requestPermission();
    // Foreground message handling
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      print('Handling a foreground message: ${message.messageId}');
      print('Message data: ${message.data}');

      if (message.notification != null) {
        print('Message also contained a notification: ${message.notification?.body}');
        // Show the notification using FlutterLocalNotificationsPlugin if needed.
      }
    });
  }


  void getToken() async {
    await FirebaseMessaging.instance.getToken().then((value){
      setState(() {
        token = value;
      });
      print('FCM Token: $token'); // Log the token or send to server
    });

  }

  void requestPermission() 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: ${settings.authorizationStatus}');
    } else if (settings.authorizationStatus == AuthorizationStatus.provisional) {
      print('User granted provisional permission');
    } else {
      print('User declined or has not accepted permission');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter FCM Demo',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FCM Example'),
        ),
        body: Center(
          child: Text('Welcome to Flutter FCM'),
        ),
      ),
    );
  }
}

Troubleshooting

  • Notifications Not Received:
    • Verify FCM token validity.
    • Check network connectivity.
    • Ensure the correct google-services.json or GoogleService-Info.plist file is configured.
    • Check if background message handling is implemented correctly.
    • On iOS, ensure that APNs certificates are correctly configured in the Apple Developer Portal and uploaded to Firebase Cloud Messaging settings page, and that push notifications capability is enabled in Xcode
  • Android Notifications Delayed:
    • Some devices might have battery optimization settings that delay notifications.
  • iOS Notifications Not Displayed:
    • Verify APNs certificates.
    • Ensure push notification settings are enabled for your app in iOS settings.

Conclusion

Integrating Firebase Cloud Messaging (FCM) into your Flutter app enables you to send reliable and timely push notifications. By following this comprehensive guide, you can effectively set up FCM, handle incoming messages, and keep your users engaged. Leveraging push notifications will help improve user retention, provide timely updates, and enhance the overall user experience of your Flutter applications.