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)
dartvoid 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:
textFCM Server → Google Play Services → FCM Plugin → onMessage Stream → Your App
iOS Flow:
textFCM 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:
text1. 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:
text1. 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
dartvoid 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:
text1. 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:
text1. 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
dartimport '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
| Aspect | Android | iOS |
|---|---|---|
| System Service | Google Play Services | APNs (Apple Push) |
| Always Running | ✅ Yes (separate app) | ✅ Yes (OS service) |
| Permission | Optional (Android 13+) | ✅ Required always |
| Background Limits | Doze mode, battery optimization | More restricted |
| Token Format | FCM token directly | APNs token → FCM token |
Important Notes
Why notifications work when app is killed:
- Android: Google Play Services is a separate app that's always running
- iOS: APNs is part of iOS system and can't be killed
- Your app doesn't need to run to receive notifications
- System handles notification display
- Your app code runs only when:
- User taps notification (killed → opens app)
- App is in foreground (onMessage)
- App is in background (background handler)
Resources: