Explain the entire flow of how FCM works from getting the FCM token and how notification received in the device? in both android and iOS? because our app is in killed state in those situation how FCM plugin we used in our app works? how notification receiving in both android and iOS? provide me the entire entire flow of architecture diagram how it will work?

#fcm#firebase#push-notifications#native-integration#android#ios

Answer

Overview

Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably send messages and notifications to devices, even when the app is in a killed state.


FCM Architecture Flow

text
┌─────────────────────────────────────────────────────────┐
│                    FCM Architecture                      │
└─────────────────────────────────────────────────────────┘

[Your Server] ──────► [Firebase FCM Server] ──────► [Device]
     │                        │                         │
     │                        │                         │
  Send API                  Routes              APNs (iOS) /
   Request                 Message              GCM (Android)
     │                        │                         │
     │                        │                         │
     ▼                        ▼                         ▼
  Payload               Processes            System Service
   + Token              & Delivers           Wakes App

Complete Flow Breakdown

Phase 1: Token Registration

Android Token Flow

dart
// 1. Flutter App Initialization
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  
  // 2. Request Permission (Android 13+)
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    badge: true,
    sound: true,
  );
  
  // 3. Get FCM Token
  String? token = await messaging.getToken();
  print('FCM Token: $token');
  
  // 4. Send token to your backend
  await sendTokenToBackend(token);
  
  // 5. Listen for token refresh
  messaging.onTokenRefresh.listen((newToken) {
    sendTokenToBackend(newToken);
  });
}

Android Native Side (Auto-handled by FCM Plugin):

kotlin
// The firebase_messaging plugin handles this automatically
// But here's what happens under the hood:

// 1. Google Play Services checks if device is registered
// 2. If not, sends request to FCM servers
// 3. FCM server generates unique token
// 4. Token sent back to device
// 5. Plugin stores token and passes to Dart side

iOS Token Flow

dart
// Same Flutter code, but iOS behavior differs
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  
  FirebaseMessaging messaging = FirebaseMessaging.instance;
  
  // iOS: MUST request permission first
  NotificationSettings settings = await messaging.requestPermission(
    alert: true,
    badge: true,
    sound: true,
  );
  
  if (settings.authorizationStatus == AuthorizationStatus.authorized) {
    // Get APNs token first, then FCM token
    String? apnsToken = await messaging.getAPNSToken();
    if (apnsToken != null) {
      String? token = await messaging.getToken();
      await sendTokenToBackend(token);
    }
  }
}

iOS Native Side:

swift
// 1. App requests permission from user
// 2. If granted, iOS registers with APNs (Apple Push Notification service)
// 3. APNs returns device token
// 4. FCM plugin converts APNs token to FCM token
// 5. FCM token sent to Firebase servers
// 6. Token passed to Dart side

Phase 2: Sending Notification

From Your Server

javascript
// Node.js example
const admin = require('firebase-admin');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

// Send to specific device
const message = {
  notification: {
    title: 'New Message',
    body: 'You have a new message from John'
  },
  data: {
    screen: 'chat',
    chatId: '123',
    senderId: '456'
  },
  token: 'USER_FCM_TOKEN_HERE'
};

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

Phase 3: Receiving Notification (App States)

1. Foreground (App Open)

dart
void setupForegroundHandler() {
  FirebaseMessaging.onMessage.listen((RemoteMessage message) {
    print('Foreground message received!');
    print('Title: ${message.notification?.title}');
    print('Body: ${message.notification?.body}');
    print('Data: ${message.data}');
    
    // Show local notification or update UI
    showLocalNotification(message);
  });
}

Android Flow:

text
FCM Server → Google Play Services → FCM Plugin → onMessage Stream → Your App

iOS Flow:

text
FCM Server → APNs → iOS System → FCM Plugin → onMessage Stream → Your App

2. Background (App Minimized)

dart
('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print('Background message: ${message.messageId}');
  
  // Handle notification
  await processBackgroundMessage(message);
}

void main() {
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  runApp(MyApp());
}

Android Background Flow:

text
1. FCM Server sends message
2. Google Play Services receives it
3. System tray notification appears automatically
4. User taps notification
5. _firebaseMessagingBackgroundHandler executes
6. App opens to specified screen

iOS Background Flow:

text
1. FCM Server sends message
2. APNs delivers to device
3. iOS shows notification banner
4. User taps notification
5. App wakes up
6. _firebaseMessagingBackgroundHandler executes

3. Terminated/Killed State

dart
void checkInitialMessage() async {
  // Check if app was opened from notification
  RemoteMessage? initialMessage = 
      await FirebaseMessaging.instance.getInitialMessage();
  
  if (initialMessage != null) {
    print('App opened from notification: ${initialMessage.messageId}');
    handleMessage(initialMessage);
  }
}

void setupMessageHandlers() {
  // Listen for messages when app is opened from background
  FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
    print('App opened from background notification');
    navigateToScreen(message.data['screen']);
  });
}

Android Killed State Flow:

text
┌──────────────────────────────────────────────────────┐
│  1. App is killed (swiped from recent apps)          │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  2. FCM Server sends notification                     │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  3. Google Play Services (always running) receives   │
│     the notification                                  │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  4. System displays notification in tray             │
│     (NO app code runs yet!)                          │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  5. User taps notification                            │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  6. Android starts app process                        │
│  7. Flutter initializes                               │
│  8. Firebase.initializeApp() called                   │
│  9. getInitialMessage() returns notification data     │
└──────────────────────────────────────────────────────┘

iOS Killed State Flow:

text
┌──────────────────────────────────────────────────────┐
│  1. App is killed (force closed by user)             │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  2. FCM Server → APNs                                 │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  3. APNs delivers to device (iOS system service)     │
│     APNs is ALWAYS running (part of iOS)             │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  4. iOS displays notification banner                  │
│     (NO app code runs!)                              │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  5. User taps notification                            │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│  6. iOS launches app                                  │
│  7. Flutter initializes                               │
│  8. getInitialMessage() returns notification          │
└──────────────────────────────────────────────────────┘

How It Works When App is Killed

Android Mechanism

Google Play Services is the key:

text
1. Google Play Services runs as a SEPARATE app
2. It's always running in background (can't be killed by user)
3. It maintains persistent connection to FCM servers
4. When notification arrives:
   - Play Services receives it
   - Creates system notification
   - Stores notification data
   - When user taps, launches your app
   - Passes notification data to app

iOS Mechanism

APNs (Apple Push Notification service) is the key:

text
1. APNs is part of iOS system (can't be disabled)
2. Maintains connection to Apple servers
3. When notification arrives:
   - APNs receives from Firebase
   - iOS system displays banner
   - Stores notification
   - When tapped, launches app
   - Delivers notification payload to app

Complete Code Example

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

// MUST be top-level function
('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(
  RemoteMessage message
) async {
  await Firebase.initializeApp();
  print('Background: ${message.notification?.title}');
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  
  // Background handler
  FirebaseMessaging.onBackgroundMessage(
    _firebaseMessagingBackgroundHandler
  );
  
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  
  void initState() {
    super.initState();
    setupFCM();
  }
  
  Future<void> setupFCM() async {
    // Request permission
    NotificationSettings settings = 
        await FirebaseMessaging.instance.requestPermission();
    
    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      // Get token
      String? token = await FirebaseMessaging.instance.getToken();
      print('FCM Token: $token');
      
      // Foreground messages
      FirebaseMessaging.onMessage.listen((RemoteMessage message) {
        print('Foreground: ${message.notification?.title}');
        showLocalNotification(message);
      });
      
      // App opened from background
      FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
        print('Opened from background');
        navigateToScreen(message.data);
      });
      
      // App opened from terminated state
      RemoteMessage? initialMessage = 
          await FirebaseMessaging.instance.getInitialMessage();
      if (initialMessage != null) {
        print('Opened from terminated');
        navigateToScreen(initialMessage.data);
      }
    }
  }
  
  void showLocalNotification(RemoteMessage message) {
    // Show notification using flutter_local_notifications
  }
  
  void navigateToScreen(Map<String, dynamic> data) {
    // Navigate based on data payload
  }
  
  
  Widget build(BuildContext context) {
    return MaterialApp(home: HomeScreen());
  }
}

Key Differences: Android vs iOS

AspectAndroidiOS
System ServiceGoogle Play ServicesAPNs (Apple Push)
Always Running✅ Yes (separate app)✅ Yes (OS service)
PermissionOptional (Android 13+)✅ Required always
Background LimitsDoze mode, battery optimizationMore restricted
Token FormatFCM token directlyAPNs token → FCM token

Important Notes

Why notifications work when app is killed:

  1. Android: Google Play Services is a separate app that's always running
  2. iOS: APNs is part of iOS system and can't be killed
  3. Your app doesn't need to run to receive notifications
  4. System handles notification display
  5. Your app code runs only when:
    • User taps notification (killed → opens app)
    • App is in foreground (onMessage)
    • App is in background (background handler)

Resources: