Firebase Cloud Messaging Implementation Guide

A comprehensive guide to implementing push notifications in your mobile app using Firebase Cloud Messaging (FCM)

Introduction to Firebase Cloud Messaging

Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably send notifications at no cost. Using FCM, you can notify a client app that new email or other data is available to sync. You can send notification messages to drive user re-engagement and retention.

Note: FCM is the successor to Google Cloud Messaging (GCM), and it inherits the reliable and scalable GCM infrastructure, plus new features.

Key Capabilities

Prerequisites

Before implementing FCM, ensure you have:

Setting Up Firebase Cloud Messaging

Step 1: Configure Firebase in Your Project

First, you need to add Firebase to your project. The process varies depending on your platform:

  1. In the Firebase console, add an Android app to your project
  2. Register the app with your package name
  3. Download the google-services.json file and place it in your app's module directory
  4. Add the Firebase SDK to your app-level build.gradle file:
dependencies {
    // Add the Firebase SDK for Google Analytics
    implementation 'com.google.firebase:firebase-analytics:21.3.0'
    
    // Add the Firebase Cloud Messaging SDK
    implementation 'com.google.firebase:firebase-messaging:23.2.0'
}

And at the bottom of the file, add:

apply plugin: 'com.google.gms.google-services'

In your project-level build.gradle file, add:

buildscript {
    repositories {
        google()
        // ...
    }
    dependencies {
        // ...
        classpath 'com.google.gms:google-services:4.3.15'
    }
}
  1. In the Firebase console, add an iOS app to your project
  2. Register the app with your bundle ID
  3. Download the GoogleService-Info.plist file
  4. Add the file to your Xcode project (drag and drop into the project navigator)
  5. Install the Firebase SDK using CocoaPods. Add to your Podfile:
pod 'Firebase/Analytics'
pod 'Firebase/Messaging'

Then run:

pod install

In your AppDelegate, initialize Firebase:

import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        return true
    }
}
  1. In the Firebase console, add a Web app to your project
  2. Register the app with a nickname
  3. Add the Firebase SDK to your web app:
<!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-messaging.js"></script>

<script>
  // Your web app's Firebase configuration
  const firebaseConfig = {
    apiKey: "your-api-key",
    authDomain: "your-auth-domain",
    projectId: "your-project-id",
    storageBucket: "your-storage-bucket",
    messagingSenderId: "your-messaging-sender-id",
    appId: "your-app-id"
  };
  
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
</script>

Step 2: Enable Cloud Messaging API

  1. Go to the Firebase Console and select your project
  2. Navigate to Project Settings (gear icon) > Cloud Messaging
  3. Make sure the Cloud Messaging API is enabled
  4. Note down the Server key for later use when sending messages from your server

Client-Side Implementation

Requesting Permission and Getting FCM Token

Before you can send notifications to a device, you need to request permission from the user and get an FCM token.

In your application class or main activity:

import com.google.firebase.messaging.FirebaseMessaging;

// Get FCM token
FirebaseMessaging.getInstance().getToken()
    .addOnCompleteListener(task -> {
        if (!task.isSuccessful()) {
            Log.w(TAG, "Fetching FCM registration token failed", task.getException());
            return;
        }

        // Get new FCM registration token
        String token = task.getResult();

        // Log and toast
        Log.d(TAG, "FCM Token: " + token);
        
        // Send token to your server
        sendRegistrationToServer(token);
    });

In your AppDelegate:

import Firebase
import FirebaseMessaging
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        
        // Request permission for notifications
        UNUserNotificationCenter.current().delegate = self
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: { _, _ in }
        )
        
        application.registerForRemoteNotifications()
        
        // Set messaging delegate
        Messaging.messaging().delegate = self
        
        return true
    }
    
    // Receive FCM token
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        print("Firebase registration token: \(String(describing: fcmToken))")
        
        // Send token to your server
        if let token = fcmToken {
            sendRegistrationToServer(token)
        }
    }
}
// Request permission
const messaging = firebase.messaging();

messaging.requestPermission()
.then(() => {
    console.log('Notification permission granted.');
    return messaging.getToken();
})
.then((token) => {
    console.log('FCM Token:', token);
    
    // Send token to your server
    sendTokenToServer(token);
})
.catch((err) => {
    console.log('Unable to get permission to notify.', err);
});

First, add the Firebase Messaging package:

flutter pub add firebase_messaging

Then in your app:

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

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

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

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

  Future setupFirebaseMessaging() async {
    // Request permission
    NotificationSettings settings = await FirebaseMessaging.instance.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );
    
    print('User granted permission: ${settings.authorizationStatus}');
    
    // Get token
    String? token = await FirebaseMessaging.instance.getToken();
    print('FCM Token: $token');
    
    // Send token to server
    if (token != null) {
      sendTokenToServer(token);
    }
    
    // Listen for token refresh
    FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
      print('FCM Token refreshed: $newToken');
      sendTokenToServer(newToken);
    });
  }
  
  void sendTokenToServer(String token) {
    // Implement your server communication here
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('FCM Example')),
        body: Center(child: Text('Firebase Cloud Messaging Example')),
      ),
    );
  }
}

First, install the required packages:

npm install @react-native-firebase/app @react-native-firebase/messaging

Then in your app:

import React, { useEffect } from 'react';
import { Alert } from 'react-native';
import messaging from '@react-native-firebase/messaging';

function App() {
  useEffect(() => {
    // Request permission
    const requestUserPermission = async () => {
      const authStatus = await messaging().requestPermission();
      const enabled =
        authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
        authStatus === messaging.AuthorizationStatus.PROVISIONAL;

      if (enabled) {
        console.log('Authorization status:', authStatus);
        getFcmToken();
      }
    }

    // Get FCM token
    const getFcmToken = async () => {
      const fcmToken = await messaging().getToken();
      if (fcmToken) {
        console.log('FCM Token:', fcmToken);
        // Send token to your server
        sendTokenToServer(fcmToken);
      }
    }

    requestUserPermission();

    // Listen to token refresh
    const unsubscribe = messaging().onTokenRefresh(token => {
      console.log('FCM Token refreshed:', token);
      sendTokenToServer(token);
    });

    return unsubscribe;
  }, []);

  const sendTokenToServer = (token) => {
    // Implement your server communication here
  };

  return (
    // Your app component
  );
}

export default App;

Handling Incoming Messages

Now that you have set up FCM and obtained a token, you need to handle incoming messages.

Create a service that extends FirebaseMessagingService:

import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;

public class MyFirebaseMessagingService extends FirebaseMessagingService {
    private static final String TAG = "MyFirebaseMsgService";

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        // Check if message contains a notification payload
        if (remoteMessage.getNotification() != null) {
            Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
            
            // Show notification
            sendNotification(remoteMessage.getNotification().getTitle(), 
                            remoteMessage.getNotification().getBody());
        }

        // Check if message contains a data payload
        if (remoteMessage.getData().size() > 0) {
            Log.d(TAG, "Message data payload: " + remoteMessage.getData());
            
            // Handle data payload
            handleDataMessage(remoteMessage.getData());
        }
    }

    @Override
    public void onNewToken(String token) {
        Log.d(TAG, "Refreshed token: " + token);

        // Send token to your app server
        sendRegistrationToServer(token);
    }

    private void sendNotification(String title, String messageBody) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
                PendingIntent.FLAG_IMMUTABLE);

        String channelId = "fcm_default_channel";
        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, channelId)
                        .setSmallIcon(R.drawable.ic_notification)
                        .setContentTitle(title)
                        .setContentText(messageBody)
                        .setAutoCancel(true)
                        .setSound(defaultSoundUri)
                        .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Since android Oreo notification channel is needed
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(0, notificationBuilder.build());
    }

    private void handleDataMessage(Map data) {
        // Process data message here
    }

    private void sendRegistrationToServer(String token) {
        // Implement this method to send token to your app server
    }
}

Don't forget to register the service in your AndroidManifest.xml:

<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

In your AppDelegate, implement the UNUserNotificationCenterDelegate methods:

// Handle notification when app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter,
                           willPresent notification: UNNotification,
                           withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    let userInfo = notification.request.content.userInfo

    // With swizzling disabled you must let Messaging know about the message
    Messaging.messaging().appDidReceiveMessage(userInfo)

    // Print message ID
    if let messageID = userInfo["gcm.message_id"] {
        print("Message ID: \(messageID)")
    }

    // Print full message
    print(userInfo)

    // Change this to your preferred presentation option
    completionHandler([[.alert, .sound]])
}

// Handle notification when user taps on it
func userNotificationCenter(_ center: UNUserNotificationCenter,
                           didReceive response: UNNotificationResponse,
                           withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo

    // Print message ID
    if let messageID = userInfo["gcm.message_id"] {
        print("Message ID: \(messageID)")
    }

    // Print full message
    print(userInfo)

    completionHandler()
}

// Handle receiving message
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // With swizzling disabled you must let Messaging know about the message
    Messaging.messaging().appDidReceiveMessage(userInfo)

    // Print message ID
    if let messageID = userInfo["gcm.message_id"] {
        print("Message ID: \(messageID)")
    }

    // Print full message
    print(userInfo)

    completionHandler(UIBackgroundFetchResult.newData)
}
// Handle foreground messages
messaging.onMessage((payload) => {
    console.log('Message received in foreground:', payload);
    
    // Display notification
    const notificationTitle = payload.notification.title;
    const notificationOptions = {
        body: payload.notification.body,
        icon: '/firebase-logo.png'
    };
    
    if (Notification.permission === 'granted') {
        new Notification(notificationTitle, notificationOptions);
    }
});

// Handle background messages
// This requires a service worker
// Create a file named firebase-messaging-sw.js in your web root:

/*
// firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/9.6.1/firebase-messaging.js');

firebase.initializeApp({
    apiKey: "your-api-key",
    authDomain: "your-auth-domain",
    projectId: "your-project-id",
    storageBucket: "your-storage-bucket",
    messagingSenderId: "your-messaging-sender-id",
    appId: "your-app-id"
});

const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
    console.log('Background message received:', payload);
    
    const notificationTitle = payload.notification.title;
    const notificationOptions = {
        body: payload.notification.body,
        icon: '/firebase-logo.png'
    };
    
    return self.registration.showNotification(notificationTitle, notificationOptions);
});
*/
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

// Define a top-level function to handle background messages
Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print("Handling a background message: ${message.messageId}");
}

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

class _MyAppState extends State {
  late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
  
  @override
  void initState() {
    super.initState();
    setupFirebaseMessaging();
    setupLocalNotifications();
  }

  Future setupFirebaseMessaging() async {
    // Set the background message handler
    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}");
        showNotification(message);
      }
    });
    
    // Handle when the app is opened from a notification
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print('A new onMessageOpenedApp event was published!');
      // Navigate to relevant page based on message data
      handleNotificationClick(message.data);
    });
    
    // Check if the app was opened from a notification
    RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
    if (initialMessage != null) {
      handleNotificationClick(initialMessage.data);
    }
  }
  
  void setupLocalNotifications() {
    flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings();
    final InitializationSettings initializationSettings = InitializationSettings(
        android: initializationSettingsAndroid,
        iOS: initializationSettingsIOS);
    flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: (String? payload) async {
      if (payload != null) {
        // Handle notification click with payload
        Map data = json.decode(payload);
        handleNotificationClick(data);
      }
    });
  }
  
  void showNotification(RemoteMessage message) async {
    RemoteNotification? notification = message.notification;
    AndroidNotification? android = message.notification?.android;
    
    if (notification != null && android != null) {
      await flutterLocalNotificationsPlugin.show(
        notification.hashCode,
        notification.title,
        notification.body,
        NotificationDetails(
          android: AndroidNotificationDetails(
            'channel_id',
            'channel_name',
            channelDescription: 'channel_description',
            importance: Importance.max,
            priority: Priority.high,
            showWhen: false,
          ),
          iOS: IOSNotificationDetails(),
        ),
        payload: json.encode(message.data),
      );
    }
  }
  
  void handleNotificationClick(Map data) {
    // Navigate to relevant page based on notification data
    print('Handling notification click with data: $data');
    // Example: Navigator.pushNamed(context, '/details', arguments: data);
  }
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('FCM Example')),
        body: Center(child: Text('Firebase Cloud Messaging Example')),
      ),
    );
  }
}
import React, { useEffect } from 'react';
import { Alert } from 'react-native';
import messaging from '@react-native-firebase/messaging';
import PushNotification from 'react-native-push-notification';

function App() {
  useEffect(() => {
    // Configure local notifications
    PushNotification.configure({
      onNotification: function (notification) {
        console.log('NOTIFICATION:', notification);
        
        // Process the notification
        
        // Required on iOS only
        notification.finish(PushNotificationIOS.FetchResult.NoData);
      },
      
      // IOS ONLY
      permissions: {
        alert: true,
        badge: true,
        sound: true,
      },
      
      popInitialNotification: true,
      requestPermissions: true,
    });
    
    // Create notification channel for Android
    PushNotification.createChannel(
      {
        channelId: "fcm_default_channel",
        channelName: "Default Channel",
        channelDescription: "A default channel for notifications",
        soundName: "default",
        importance: 4,
        vibrate: true,
      },
      (created) => console.log(`Channel created: ${created}`)
    );
    
    // Handle foreground messages
    const unsubscribe = messaging().onMessage(async remoteMessage => {
      console.log('A new FCM message arrived!', remoteMessage);
      
      // Display local notification
      PushNotification.localNotification({
        channelId: "fcm_default_channel",
        title: remoteMessage.notification?.title,
        message: remoteMessage.notification?.body || '',
        data: remoteMessage.data,
      });
    });
    
    // Handle background/quit state messages
    messaging().setBackgroundMessageHandler(async remoteMessage => {
      console.log('Message handled in the background!', remoteMessage);
    });
    
    // Check if app was opened from a notification
    messaging()
      .getInitialNotification()
      .then(remoteMessage => {
        if (remoteMessage) {
          console.log(
            'Notification caused app to open from quit state:',
            remoteMessage,
          );
          // Navigate based on the notification data
          handleNotificationOpen(remoteMessage);
        }
      });
    
    // Handle notification open when app is in background
    messaging().onNotificationOpenedApp(remoteMessage => {
      console.log(
        'Notification caused app to open from background state:',
        remoteMessage,
      );
      // Navigate based on the notification data
      handleNotificationOpen(remoteMessage);
    });
    
    return unsubscribe;
  }, []);
  
  const handleNotificationOpen = (remoteMessage) => {
    // Navigate to relevant screen based on notification data
    console.log('Handling notification open with data:', remoteMessage.data);
    // Example: navigation.navigate('Details', { id: remoteMessage.data.id });
  };
  
  return (
    // Your app component
  );
}

export default App;

Server-Side Implementation

Storing FCM Tokens

When a client app registers with FCM, it receives a registration token. You should store this token on your server to send messages to specific devices.

// Example server code (Node.js with Express and MongoDB)
const express = require('express');
const mongoose = require('mongoose');
const app = express();

app.use(express.json());

// Define a schema for storing FCM tokens
const tokenSchema = new mongoose.Schema({
  userId: { type: String, required: true },
  token: { type: String, required: true },
  platform: { type: String, enum: ['android', 'ios', 'web'], required: true },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

const Token = mongoose.model('Token', tokenSchema);

// API endpoint to register/update FCM token
app.post('/api/register-token', async (req, res) => {
  try {
    const { userId, token, platform } = req.body;
    
    // Validate input
    if (!userId || !token || !platform) {
      return res.status(400).json({ error: 'Missing required fields' });
    }
    
    // Update token if it exists, otherwise create a new one
    const result = await Token.findOneAndUpdate(
      { userId, platform },
      { token, updatedAt: Date.now() },
      { upsert: true, new: true }
    );
    
    res.status(200).json({ success: true, data: result });
  } catch (error) {
    console.error('Error registering token:', error);
    res.status(500).json({ error: 'Server error' });
  }
});

Sending Messages from Your Server

You can send messages to devices using the FCM HTTP v1 API or the Firebase Admin SDK.

Using the Firebase Admin SDK:

const admin = require('firebase-admin');
const serviceAccount = require('./path/to/serviceAccountKey.json');

// Initialize the app with a service account
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

// Send a message to a specific device
async function sendNotificationToDevice(token, title, body, data = {}) {
  try {
    const message = {
      notification: {
        title,
        body
      },
      data,
      token
    };
    
    const response = await admin.messaging().send(message);
    console.log('Successfully sent message:', response);
    return response;
  } catch (error) {
    console.error('Error sending message:', error);
    throw error;
  }
}

// Send a message to multiple devices
async function sendNotificationToDevices(tokens, title, body, data = {}) {
  try {
    const message = {
      notification: {
        title,
        body
      },
      data,
      tokens // Array of tokens
    };
    
    const response = await admin.messaging().sendMulticast(message);
    console.log(`${response.successCount} messages were sent successfully`);
    return response;
  } catch (error) {
    console.error('Error sending messages:', error);
    throw error;
  }
}

// Send a message to a topic
async function sendNotificationToTopic(topic, title, body, data = {}) {
  try {
    const message = {
      notification: {
        title,
        body
      },
      data,
      topic
    };
    
    const response = await admin.messaging().send(message);
    console.log('Successfully sent message to topic:', response);
    return response;
  } catch (error) {
    console.error('Error sending message to topic:', error);
    throw error;
  }
}

// Example API endpoint to send notification
app.post('/api/send-notification', async (req, res) => {
  try {
    const { userId, title, body, data } = req.body;
    
    // Get the user's FCM token from the database
    const tokenDoc = await Token.findOne({ userId });
    
    if (!tokenDoc) {
      return res.status(404).json({ error: 'User token not found' });
    }
    
    const response = await sendNotificationToDevice(tokenDoc.token, title, body, data);
    res.status(200).json({ success: true, response });
  } catch (error) {
    console.error('Error sending notification:', error);
    res.status(500).json({ error: 'Server error' });
  }
});

Using the Firebase Admin SDK:

import firebase_admin
from firebase_admin import credentials, messaging

# Initialize the app with a service account
cred = credentials.Certificate('path/to/serviceAccountKey.json')
firebase_admin.initialize_app(cred)

# Send a message to a specific device
def send_notification_to_device(token, title, body, data=None):
    if data is None:
        data = {}
    
    message = messaging.Message(
        notification=messaging.Notification(
            title=title,
            body=body
        ),
        data=data,
        token=token
    )
    
    try:
        response = messaging.send(message)
        print('Successfully sent message:', response)
        return response
    except Exception as e:
        print('Error sending message:', e)
        raise e

# Send a message to multiple devices
def send_notification_to_devices(tokens, title, body, data=None):
    if data is None:
        data = {}
    
    message = messaging.MulticastMessage(
        notification=messaging.Notification(
            title=title,
            body=body
        ),
        data=data,
        tokens=tokens
    )
    
    try:
        response = messaging.send_multicast(message)
        print(f'{response.success_count} messages were sent successfully')
        return response
    except Exception as e:
        print('Error sending messages:', e)
        raise e

# Send a message to a topic
def send_notification_to_topic(topic, title, body, data=None):
    if data is None:
        data = {}
    
    message = messaging.Message(
        notification=messaging.Notification(
            title=title,
            body=body
        ),
        data=data,
        topic=topic
    )
    
    try:
        response = messaging.send(message)
        print('Successfully sent message to topic:', response)
        return response
    except Exception as e:
        print('Error sending message to topic:', e)
        raise e

# Example Flask API endpoint to send notification
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/api/send-notification', methods=['POST'])
def send_notification():
    try:
        data = request.json
        user_id = data.get('userId')
        title = data.get('title')
        body = data.get('body')
        custom_data = data.get('data', {})
        
        # Get the user's FCM token from the database
        # This is a placeholder - implement your database logic
        token = get_user_token(user_id)
        
        if not token:
            return jsonify({'error': 'User token not found'}), 404
        
        response = send_notification_to_device(token, title, body, custom_data)
        return jsonify({'success': True, 'response': response}), 200
    except Exception as e:
        print('Error sending notification:', e)
        return jsonify({'error': 'Server error'}), 500

def get_user_token(user_id):
    # Implement your database logic to retrieve the token
    # This is a placeholder
    return "user_fcm_token"

if __name__ == '__main__':
    app.run(debug=True)

Using the Firebase Admin SDK:

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.messaging.*;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

@SpringBootApplication
@RestController
public class FcmApplication {

    public static void main(String[] args) throws IOException {
        // Initialize Firebase Admin SDK
        FileInputStream serviceAccount = new FileInputStream("path/to/serviceAccountKey.json");
        FirebaseOptions options = FirebaseOptions.builder()
                .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                .build();
        FirebaseApp.initializeApp(options);
        
        SpringApplication.run(FcmApplication.class, args);
    }
    
    // Send a message to a specific device
    public String sendNotificationToDevice(String token, String title, String body, Map data) throws FirebaseMessagingException {
        Notification notification = Notification.builder()
                .setTitle(title)
                .setBody(body)
                .build();
                
        Message message = Message.builder()
                .setNotification(notification)
                .putAllData(data)
                .setToken(token)
                .build();
                
        String response = FirebaseMessaging.getInstance().send(message);
        System.out.println("Successfully sent message: " + response);
        return response;
    }
    
    // Send a message to multiple devices
    public BatchResponse sendNotificationToDevices(List tokens, String title, String body, Map data) throws FirebaseMessagingException {
        Notification notification = Notification.builder()
                .setTitle(title)
                .setBody(body)
                .build();
                
        MulticastMessage message = MulticastMessage.builder()
                .setNotification(notification)
                .putAllData(data)
                .addAllTokens(tokens)
                .build();
                
        BatchResponse response = FirebaseMessaging.getInstance().sendMulticast(message);
        System.out.println(response.getSuccessCount() + " messages were sent successfully");
        return response;
    }
    
    // Send a message to a topic
    public String sendNotificationToTopic(String topic, String title, String body, Map data) throws FirebaseMessagingException {
        Notification notification = Notification.builder()
                .setTitle(title)
                .setBody(body)
                .build();
                
        Message message = Message.builder()
                .setNotification(notification)
                .putAllData(data)
                .setTopic(topic)
                .build();
                
        String response = FirebaseMessaging.getInstance().send(message);
        System.out.println("Successfully sent message to topic: " + response);
        return response;
    }
    
    // Example API endpoint to send notification
    @PostMapping("/api/send-notification")
    public ResponseEntity sendNotification(@RequestBody NotificationRequest request) {
        try {
            // Get the user's FCM token from the database
            // This is a placeholder - implement your database logic
            String token = getUserToken(request.getUserId());
            
            if (token == null) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User token not found");
            }
            
            String response = sendNotificationToDevice(token, request.getTitle(), request.getBody(), request.getData());
            return ResponseEntity.ok(response);
        } catch (FirebaseMessagingException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error sending notification");
        }
    }
    
    private String getUserToken(String userId) {
        // Implement your database logic to retrieve the token
        // This is a placeholder
        return "user_fcm_token";
    }
    
    // Request model
    public static class NotificationRequest {
        private String userId;
        private String title;
        private String body;
        private Map data;
        
        // Getters and setters
    }
}

Topic Messaging

FCM topic messaging allows you to send a message to multiple devices that have opted in to a particular topic.

Subscribe to a topic on the client:

// Android
FirebaseMessaging.getInstance().subscribeToTopic("news")
    .addOnCompleteListener(task -> {
        if (task.isSuccessful()) {
            Log.d(TAG, "Subscribed to news topic");
        } else {
            Log.e(TAG, "Failed to subscribe to news topic", task.getException());
        }
    });

// iOS
Messaging.messaging().subscribe(toTopic: "news") { error in
  if let error = error {
    print("Failed to subscribe to news topic: \(error)")
  } else {
    print("Subscribed to news topic")
  }
}

// Flutter
FirebaseMessaging.instance.subscribeToTopic('news')
    .then((value) => print('Subscribed to news topic'))
    .catchError((error) => print('Failed to subscribe to news topic: $error'));

// React Native
messaging()
    .subscribeToTopic('news')
    .then(() => console.log('Subscribed to news topic'))
    .catch(error => console.log('Failed to subscribe to news topic:', error));

Send a message to a topic from the server:

// Node.js
const message = {
  notification: {
    title: 'Breaking News',
    body: 'New article available'
  },
  topic: 'news'
};

admin.messaging().send(message)
  .then((response) => {
    console.log('Successfully sent message to topic:', response);
  })
  .catch((error) => {
    console.error('Error sending message to topic:', error);
  });

// Python
message = messaging.Message(
    notification=messaging.Notification(
        title='Breaking News',
        body='New article available'
    ),
    topic='news'
)

response = messaging.send(message)
print('Successfully sent message to topic:', response)

// Java
Message message = Message.builder()
    .setNotification(Notification.builder()
        .setTitle("Breaking News")
        .setBody("New article available")
        .build())
    .setTopic("news")
    .build();

String response = FirebaseMessaging.getInstance().send(message);
System.out.println("Successfully sent message to topic: " + response);

Testing Your FCM Implementation

Testing from Firebase Console

The easiest way to test your FCM implementation is to send a test message from the Firebase Console:

  1. Go to the Firebase Console and select your project
  2. Navigate to Messaging (in the left sidebar)
  3. Click "Send your first message"
  4. Compose your notification (title, text, image)
  5. Under "Target", select one of the following:
    • User segment - to target specific user groups
    • Topic - to send to devices subscribed to a topic
    • Single device - to test with a specific FCM token
  6. Complete the setup and send the test message

Testing with cURL

You can also test your FCM implementation using cURL commands:

curl -X POST -H "Authorization: key=YOUR_SERVER_KEY" -H "Content-Type: application/json" \
  -d '{
    "to": "DEVICE_FCM_TOKEN",
    "notification": {
      "title": "Test Notification",
      "body": "This is a test notification from cURL"
    },
    "data": {
      "key1": "value1",
      "key2": "value2"
    }
  }' \
  https://fcm.googleapis.com/fcm/send

Testing with Postman

Postman provides a user-friendly interface for testing API calls:

  1. Create a new POST request to https://fcm.googleapis.com/fcm/send
  2. Add headers:
    • Authorization: key=YOUR_SERVER_KEY
    • Content-Type: application/json
  3. Add the request body:
    {
      "to": "DEVICE_FCM_TOKEN",
      "notification": {
        "title": "Test Notification",
        "body": "This is a test notification from Postman"
      },
      "data": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  4. Send the request and check if the notification is received on the device

Best Practices for FCM Implementation

Token Management

Message Payload

User Experience

Security

Performance

Troubleshooting Common Issues

Notifications Not Received

Notification Displayed Incorrectly

Background/Foreground Handling Issues

API Errors

Common Error Codes:
  • 400 Bad Request - Check your payload format and ensure it's valid JSON
  • 401 Unauthorized - Verify your server key is correct and has not expired
  • 403 Forbidden - Your server key might not have permission to send messages
  • 404 Not Found - The FCM token might be invalid or the device might have uninstalled your app
  • 429 Too Many Requests - You're sending too many messages too quickly; implement rate limiting

Conclusion

Firebase Cloud Messaging provides a reliable and scalable solution for sending push notifications to your users across different platforms. By following the implementation steps and best practices outlined in this guide, you can effectively integrate FCM into your application and enhance user engagement.

Remember to:

With FCM, you can keep your users informed and engaged with timely and relevant notifications, ultimately improving user retention and satisfaction.