FCM zwischen iOS, Android und React Native sauber verdrahten
Push Notifications sind kein einheitliches Feature, sondern ein Zusammenspiel aus APNs, Firebase Cloud Messaging, Betriebssystem-Berechtigungen und App-Zustand. Wer FCM in React Native ohne sauberes Setup für Foreground, Background und Quit State einbindet, verliert Notifications genau dann, wenn Nutzer sie am dringendsten brauchen. Dieser Artikel zeigt, wie Firebase-Projekt, FCM SDK, Berechtigungen, Payloads, Deep-Linking und Token-Management für React Native zuverlässig zusammenspielen.
Inhaltsverzeichnis
- 1. Warum Push Notifications plattformübergreifend eine Herausforderung sind
- 2. Firebase-Projekt einrichten
- 3. FCM SDK Setup in React Native
- 4. Berechtigungen anfragen
- 5. Foreground-, Background- und Quit-State-Handling
- 6. Notification-Payload-Gestaltung
- 7. Deep-Linking aus Notifications
- 8. Token-Management, Topics und Segmentierung
- 9. FCM im Vergleich zu Expo Notifications und OneSignal
- 10. Zusammenfassung
- 11. FAQ
1. Warum Push Notifications plattformübergreifend eine Herausforderung sind
Unter der Oberfläche verbergen sich bei Push Notifications zwei vollständig getrennte Zustellsysteme. Auf iOS läuft jede Nachricht letztlich über den Apple Push Notification Service, kurz APNs, ein proprietäres Protokoll mit eigener Zertifikatsverwaltung und eigenen Payload-Grenzen. Auf Android übernimmt Firebase Cloud Messaging die Zustellung direkt über Google-Infrastruktur. FCM selbst reicht Nachrichten für iOS-Geräte lediglich an APNs weiter, agiert dort also als Vermittler, während es für Android der primäre Kanal ist. Wer diese Zweiteilung ignoriert und ein einziges, plattformunabhängiges Verhalten erwartet, stößt schnell auf Inkonsistenzen bei Zustellzeit, Payload-Struktur und Priorisierung.
Erschwerend kommt hinzu, dass sich der App-Zustand zum Zeitpunkt der Zustellung fundamental unterscheidet und jede Plattform anders reagiert. Eine App im Vordergrund erhält die Nachricht als reines Datenereignis, ohne dass das Betriebssystem automatisch eine sichtbare Notification anzeigt. Eine App im Hintergrund lässt das Betriebssystem die Anzeige übernehmen, sofern die Nachricht als Notification-Message und nicht als reine Data-Message ankommt. Eine vollständig beendete App, der sogenannte Quit State, kann unter iOS und Android unterschiedlich behandelt werden, insbesondere wenn der Nutzer die App durch Force-Quit beendet hat, wonach manche Zustellwege von Google und Apple absichtlich blockiert werden.
Für React Native kommt eine dritte Ebene hinzu: die Bridge zwischen nativen SDKs und JavaScript. Sowohl APNs als auch FCM liefern ihre Ereignisse zunächst nativ aus, bevor @react-native-firebase/messaging sie als JavaScript-Events an die App-Logik weiterreicht. Dieser Umweg bedeutet, dass Listener zum richtigen Zeitpunkt registriert sein müssen, meist noch bevor die React-Komponentenbaum-Initialisierung abgeschlossen ist, da sonst Nachrichten verloren gehen, die eintreffen, während die App gerade erst hochfährt.
2. Firebase-Projekt einrichten
Die Basis für Firebase Cloud Messaging ist ein Firebase-Projekt in der Firebase Console, dem für jede Zielplattform eine eigene App-Registrierung hinzugefügt wird. Für Android wird die App über den Package-Namen registriert, exakt übereinstimmend mit dem applicationId in android/app/build.gradle. Für iOS erfolgt die Registrierung über die Bundle-ID, die wiederum exakt dem in Xcode konfigurierten Bundle-Identifier entsprechen muss. Jede Abweichung, und sei es nur ein einzelner Buchstabe in Groß- oder Kleinschreibung, führt dazu, dass FCM-Nachrichten die App später nicht erreichen, ohne dass ein aussagekräftiger Fehler auftaucht.
Für Android lädt die Firebase Console eine google-services.json herunter, die ins Verzeichnis android/app/ kopiert wird und die Firebase-Projektkonfiguration inklusive API-Schlüssel und Absender-ID enthält. Für iOS ist das Gegenstück die GoogleService-Info.plist, die im Xcode-Projekt dem Target hinzugefügt werden muss, nicht nur ins Dateisystem kopiert, da Xcode sie sonst nicht ins App-Bundle aufnimmt. Zusätzlich muss unter iOS im Firebase-Projekt der APNs-Authentifizierungsschlüssel hinterlegt werden, eine .p8-Datei aus dem Apple Developer Portal, ohne die FCM Nachrichten zwar entgegennimmt, aber nie an Apples APNs weiterreichen kann.
{
"project_info": {
"project_id": "mironsoft-push-demo",
"storage_bucket": "mironsoft-push-demo.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:123456789012:android:abc123def456",
"android_client_info": {
"package_name": "de.mironsoft.pushdemo"
}
},
"api_key": [
{ "current_key": "AIzaSyDUMMY-REPLACE-WITH-REAL-KEY" }
]
}
],
"configuration_version": "1"
}
3. FCM SDK Setup in React Native
Der React-Native-Firebase-Community-Fork, mittlerweile das offiziell empfohlene Modul für Firebase-Integration, teilt sich in mehrere Pakete auf, von denen @react-native-firebase/app als Basis-Modul zwingend erforderlich ist. Es initialisiert die native Firebase-App-Instanz aus google-services.json und GoogleService-Info.plist und muss vor jedem anderen Firebase-Paket geladen werden. Darauf aufbauend liefert @react-native-firebase/messaging die eigentliche FCM-Funktionalität: Token-Abruf, Listener für eingehende Nachrichten und Berechtigungsverwaltung.
Unter iOS ist nach der Paketinstallation ein pod install im ios/-Verzeichnis notwendig, da die nativen Firebase-SDKs über CocoaPods eingebunden werden. Zusätzlich muss in Xcode unter den Capabilities des Targets Push Notifications und Background Modes mit aktiviertem Remote Notifications Häkchen gesetzt werden, ohne die das Betriebssystem eingehende FCM-Nachrichten im Hintergrund verwirft. Unter Android ist ab Firebase-Messaging-Version 23 zusätzlich das Google-Services-Gradle-Plugin in android/build.gradle und android/app/build.gradle erforderlich, das die google-services.json zur Build-Zeit verarbeitet.
Ein häufig übersehener Schritt ist die Registrierung des Background-Message-Handlers außerhalb jeder React-Komponente, direkt im Einstiegspunkt der App, meist in index.js, noch vor AppRegistry.registerComponent. Wird der Handler stattdessen innerhalb einer Komponente registriert, verpasst er Nachrichten, die eintreffen, bevor die Komponente gemountet ist, was insbesondere beim App-Start aus dem Quit State regelmäßig zu stillen Ausfällen führt.
# Install the core Firebase module and the messaging module
npm install @react-native-firebase/app @react-native-firebase/messaging
# iOS: install native CocoaPods dependencies
cd ios && pod install && cd ..
# Android: no extra step needed beyond the Gradle plugin
# already wired into android/build.gradle and app/build.gradle
# Verify the installed native modules are linked correctly
npx react-native run-ios
npx react-native run-android
4. Berechtigungen anfragen
Unter iOS ist die Berechtigungsanfrage seit jeher explizit: Ohne einen Aufruf von messaging().requestPermission() zeigt das Betriebssystem niemals eine Notification an, selbst wenn FCM die Nachricht technisch korrekt zustellt. Die Rückgabe unterscheidet zwischen AUTHORIZED, PROVISIONAL, für stille Zustellung ins Notification-Center ohne Banner, und DENIED. Der Aufruf sollte im Idealfall an einen sinnvollen Moment im Onboarding gekoppelt werden, statt ihn direkt beim ersten App-Start auszulösen, da eine einmal verweigerte Berechtigung ohne manuellen Umweg über die iOS-Einstellungen nicht erneut per Dialog abgefragt werden kann.
Nach erteilter Berechtigung reicht der JavaScript-Aufruf allein unter iOS noch nicht aus. Das native AppDelegate muss sich zusätzlich für Remote Notifications bei Apple registrieren und den erhaltenen APNs-Device-Token an Firebase weiterreichen, damit FCM Nachrichten überhaupt an dieses Gerät adressieren kann. Ohne diese native Weiterleitung bleibt der Firebase-Token zwar gültig, die Verbindung zwischen APNs-Identität und Firebase-Registrierung fehlt aber, und Nachrichten laufen ins Leere.
// AppDelegate.swift - forward the APNs device token to Firebase for FCM
import FirebaseMessaging
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
// Hand the raw APNs token over to Firebase so FCM can route messages
Messaging.messaging().apnsToken = deviceToken
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
print("APNs registration failed: \(error.localizedDescription)")
}
Unter Android war bis Version 12 überhaupt keine explizite Runtime-Permission für Notifications notwendig, Apps durften stillschweigend Notifications anzeigen. Seit Android 13 (API-Level 33) ist POST_NOTIFICATIONS eine reguläre gefährliche Berechtigung, die zur Laufzeit über PermissionsAndroid.request() angefragt werden muss, zusätzlich zum Manifest-Eintrag. Wird die Berechtigung unter Android 13+ nicht aktiv angefragt, liefert FCM zwar weiterhin Data-Messages im Hintergrund aus, unterdrückt aber jede sichtbare Notification-Anzeige vollständig, ein Zustand, der im Testing leicht übersehen wird, weil die App scheinbar normal funktioniert.
5. Foreground-, Background- und Quit-State-Handling
Für jeden der drei App-Zustände stellt @react-native-firebase/messaging einen eigenen Listener bereit, und alle drei müssen unabhängig voneinander behandelt werden. messaging().onMessage() feuert, wenn die App im Vordergrund läuft. In diesem Fall zeigt das Betriebssystem selbst dann keine sichtbare Notification an, wenn die eingehende Nachricht als Notification-Message markiert ist, was bewusstes Design ist: Die App soll selbst entscheiden, ob und wie ein Hinweis angezeigt wird, etwa als In-App-Banner statt als System-Notification. messaging().setBackgroundMessageHandler() übernimmt Nachrichten, während die App im Hintergrund läuft, aber noch im Speicher gehalten wird, und läuft dabei nativ, isoliert von der aktiven JavaScript-Instanz der sichtbaren App.
Der Quit State ist der komplizierteste Fall. Wurde die App vollständig beendet, muss messaging().getInitialNotification() beim App-Start abgefragt werden, um festzustellen, ob der Start durch einen Tap auf eine Notification ausgelöst wurde. Wichtig: Apple und Google behandeln einen durch den Nutzer manuell erzwungenen Force-Quit anders als ein System-seitiges Beenden im Hintergrund. Nach einem Force-Quit liefert Android in bestimmten Konfigurationen gar keine FCM-Nachrichten mehr aus, bis die App erneut manuell geöffnet wird, ein Verhalten, das sich nicht durch App-Code umgehen lässt und bei der Erwartungshaltung an Zustellraten berücksichtigt werden muss.
Alle drei Listener sollten so früh wie möglich registriert werden, idealerweise in einer zentralen Initialisierungsfunktion, die sowohl beim App-Start als auch beim erneuten Foregrounding aufgerufen wird. Ein häufiger Fehler ist, den Foreground-Listener erst nach dem ersten Render einer tief verschachtelten Komponente zu registrieren, wodurch Nachrichten verloren gehen, die exakt in diesem kurzen Zeitfenster eintreffen.
// notifications/setup.js - register listeners for all three app states
import messaging from '@react-native-firebase/messaging';
// Background and quit-state handler, registered outside any component,
// typically in index.js before AppRegistry.registerComponent
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
console.log('Message handled in the background:', remoteMessage.messageId);
});
export function registerForegroundListeners(onNotificationTap) {
// Foreground messages: OS does not show a system banner automatically
const unsubscribeOnMessage = messaging().onMessage(async (remoteMessage) => {
console.log('Foreground message received:', remoteMessage.notification);
// Show an in-app banner or toast here instead of relying on the OS
});
// App opened from background state by tapping a notification
const unsubscribeOpenedApp = messaging().onNotificationOpenedApp((remoteMessage) => {
onNotificationTap(remoteMessage);
});
// App opened from quit state by tapping a notification
messaging()
.getInitialNotification()
.then((remoteMessage) => {
if (remoteMessage) {
onNotificationTap(remoteMessage);
}
});
return () => {
unsubscribeOnMessage();
unsubscribeOpenedApp();
};
}
6. Notification-Payload-Gestaltung
Eine FCM-Nachricht kann als Notification-Message, als Data-Message oder als Kombination aus beidem verschickt werden, und die Wahl entscheidet über das Verhalten in jedem App-Zustand. Eine reine Notification-Message mit einem notification-Objekt lässt das Betriebssystem die Anzeige automatisch übernehmen, wenn die App im Hintergrund oder beendet ist, feuert aber im Vordergrund nur den onMessage-Listener ohne sichtbare Anzeige. Eine reine Data-Message mit einem data-Objekt und ohne notification-Feld erreicht die App in jedem Zustand als reines JavaScript-Ereignis, zeigt aber nie automatisch etwas an, die App muss die Anzeige selbst übernehmen, etwa über eine lokale Notification-Bibliothek.
Für die meisten Produktivsysteme ist die Kombination aus beiden Feldern der pragmatischste Ansatz: Das notification-Objekt sorgt für die automatische Anzeige im Hintergrund und Quit State, das begleitende data-Objekt trägt zusätzliche Metadaten wie Bildschirm-Ziel, ID eines referenzierten Datensatzes oder Kategorie, die beim Tap für Deep-Linking ausgewertet werden. Wichtig ist die harte Payload-Grenze: FCM begrenzt die Gesamtgröße einer Nachricht auf 4 Kilobyte, was für Bilder oder größere strukturierte Daten nicht ausreicht, weshalb solche Inhalte per URL referenziert und erst nach Empfang der Notification nachgeladen werden müssen.
Priorität ist ein weiterer Stellhebel: Auf Android unterscheidet FCM zwischen normaler und hoher Priorität, wobei nur hohe Priorität garantiert, dass Doze-Mode und App-Standby die Zustellung nicht verzögern. Unter iOS steuert das APNs-Feld content-available in Kombination mit stiller Zustellung, ob eine Nachricht als Background-Fetch-Trigger behandelt wird, ohne dass der Nutzer überhaupt eine sichtbare Notification sieht, ein Mechanismus, der sich gut für stille Datensynchronisation eignet, aber vom System rate-limitiert wird und daher nicht für zeitkritische Inhalte verlässlich ist.
7. Deep-Linking aus Notifications
Der eigentliche Wert einer Push Notification entsteht oft erst durch den Tap: Der Nutzer soll nicht einfach die App öffnen, sondern direkt auf dem relevanten Screen landen, etwa eine bestimmte Bestellung, ein Chat-Verlauf oder ein Produktdetail. Das dafür nötige Deep-Linking baut auf denselben Listenern auf, die bereits für das App-Zustand-Handling registriert wurden: onNotificationOpenedApp für den Tap aus dem Hintergrund und getInitialNotification für den Tap aus dem Quit State. Beide liefern das vollständige remoteMessage-Objekt inklusive des data-Felds, aus dem sich die Ziel-Route extrahieren lässt.
Die eigentliche Navigation läuft über eine Referenz auf den React-Navigation-Container, meist über createNavigationContainerRef(), weil zum Zeitpunkt des Notification-Taps aus dem Quit State die Navigationsstruktur unter Umständen noch nicht vollständig gemountet ist. Ein robustes Pattern verzögert die eigentliche Navigation, bis der Container tatsächlich bereit ist, statt sie sofort und ungeprüft auszuführen, da ein zu früher Navigationsaufruf sonst stillschweigend ins Leere läuft und der Nutzer trotz Tap auf der Startseite landet.
// navigation/notificationLinking.js - deep link into a specific screen on tap
import { createNavigationContainerRef } from '@react-navigation/native';
export const navigationRef = createNavigationContainerRef();
function navigateWhenReady(name, params) {
if (navigationRef.isReady()) {
navigationRef.navigate(name, params);
return;
}
// Retry shortly after mount if the navigator is not ready yet
setTimeout(() => navigateWhenReady(name, params), 300);
}
export function handleNotificationTap(remoteMessage) {
const { screen, referenceId } = remoteMessage.data ?? {};
switch (screen) {
case 'order-detail':
navigateWhenReady('OrderDetail', { orderId: referenceId });
break;
case 'chat-thread':
navigateWhenReady('ChatThread', { threadId: referenceId });
break;
default:
navigateWhenReady('Home');
}
}
8. Token-Management, Topics und Segmentierung
Jede Geräteinstallation erhält von FCM einen eindeutigen Registrierungstoken über messaging().getToken(), der als Zieladresse für serverseitig ausgelöste Nachrichten an genau dieses eine Gerät dient. Dieser Token ist nicht dauerhaft stabil: Er kann sich ändern, wenn die App neu installiert wird, App-Daten gelöscht werden oder Firebase intern rotiert, weshalb messaging().onTokenRefresh() zwingend registriert werden muss, um den aktuellen Token bei jeder Änderung ans eigene Backend zu übermitteln. Ein veralteter, nicht aktualisierter Token führt zu stillen Zustellfehlern, die im Firebase-Dashboard oft erst mit Verzögerung sichtbar werden.
Für Broadcast-Nachrichten an größere Nutzergruppen, statt an einzelne Geräte, sind Topics das passende Werkzeug. Mit messaging().subscribeToTopic('promotions') abonniert ein Gerät ein Thema, an das sich anschließend serverseitig ohne Kenntnis einzelner Tokens Nachrichten senden lassen. Topics eignen sich gut für breite, wenig personalisierte Kategorien wie Produktankündigungen oder Wartungsfenster, sind aber für individualisierte Segmentierung, etwa nach Kaufhistorie oder Nutzerverhalten, ungeeignet, da FCM selbst keine serverseitige Filterlogik pro Topic anbietet.
Für feinere Segmentierung, etwa Nutzer mit abgelaufenem Abonnement oder Warenkorbabbrechern, bleibt nur der Weg über eine eigene Datenbanktabelle, die Tokens mit Nutzerattributen verknüpft, und serverseitige Batch-Versendung an gefilterte Token-Listen über die Firebase Admin SDK. Wichtig dabei: Tokens, die von Firebase als ungültig zurückgemeldet werden, etwa nach Deinstallation der App, müssen aktiv aus der eigenen Datenbank entfernt werden, sonst wächst die Zustellliste mit toten Einträgen, die die Zustellrate in Analytics künstlich verschlechtern.
9. FCM im Vergleich zu Expo Notifications und OneSignal
Firebase Cloud Messaging ist nicht die einzige Option für Push Notifications in React Native. Expo Notifications kapselt FCM und APNs hinter einer eigenen, vereinfachten API und einem eigenen Push-Token-Format, während OneSignal als spezialisierter Drittanbieter-Dienst zusätzliche Segmentierungs- und Analytics-Funktionen auf FCM und APNs aufsetzt. Die folgende Tabelle stellt die drei Ansätze entlang praxisrelevanter Dimensionen gegenüber.
| Dimension | Firebase Cloud Messaging | Expo Notifications | OneSignal |
|---|---|---|---|
| Setup-Komplexität | Mittel, eigenes Firebase-Projekt und native Konfiguration nötig | Gering, ein Expo-Push-Token für beide Plattformen | Gering bis mittel, eigenes Dashboard und SDK-Integration |
| Bare-Workflow-Pflicht | Ja, native Module und google-services.json erforderlich | Nein, funktioniert im Expo Managed Workflow | Ja für vollen Funktionsumfang, Expo-Plugin verfügbar |
| Segmentierung | Nur Topics, keine serverseitige Filterlogik | Keine eigene Segmentierung, nur einzelne Tokens | Umfangreich, Segmente nach Verhalten und Attributen |
| Analytics | Grundlegend über Firebase Console | Minimal, kein eigenes Analytics-Dashboard | Ausführlich, Zustellraten, Öffnungsraten, A/B-Tests |
| Preismodell | Kostenlos ohne Mengenbegrenzung | Kostenlos, läuft technisch über FCM/APNs | Kostenlose Stufe begrenzt, kostenpflichtig bei Skalierung |
In der Praxis ist Firebase Cloud Messaging die richtige Wahl, wenn ohnehin ein Bare React Native Workflow und eigenes Backend existieren und volle Kontrolle über Payloads und Zustellverhalten gewünscht ist. Expo Notifications eignet sich für Teams, die im Managed Workflow bleiben und einfache Anwendungsfälle ohne komplexe Segmentierung abdecken. OneSignal lohnt sich, sobald Marketing-Teams eigenständig Kampagnen und A/B-Tests ohne Entwickler-Involvement fahren wollen, verursacht dafür aber laufende Kosten und eine zusätzliche Abhängigkeit neben FCM selbst.
Mironsoft
React Native Push-Infrastruktur und Mobile-Engagement-Setups
Push Notifications, die zuverlässig ankommen, egal in welchem App-Zustand?
Wir richten Firebase Cloud Messaging für iOS und Android sauber ein, verdrahten Foreground-, Background- und Quit-State-Handling korrekt und bauen Deep-Linking und Topic-Segmentierung, die auch bei Skalierung stabil bleiben.
FCM-Setup
Firebase-Projekt, APNs-Zertifikate und @react-native-firebase/messaging fehlerfrei verdrahtet
App-Zustand-Handling
Foreground, Background und Quit State zuverlässig abgedeckt, inklusive Deep-Linking
Token & Segmentierung
Token-Rotation, Topic-Subscriptions und Backend-Anbindung für skalierbare Zustellung
10. Zusammenfassung
Push Notifications mit Firebase Cloud Messaging funktionieren in React Native zuverlässig, sobald man die zwei getrennten Zustellsysteme, APNs für iOS und FCM für Android, sowie die drei App-Zustände Foreground, Background und Quit State bewusst getrennt behandelt. Das Firebase-Projekt mit google-services.json, GoogleService-Info.plist und APNs-Authentifizierungsschlüssel bildet das Fundament, @react-native-firebase/messaging liefert die JavaScript-Schnittstelle für Berechtigungen, Listener und Token-Management.
Die Payload-Wahl zwischen Notification- und Data-Message entscheidet über das Verhalten in jedem Zustand, während Deep-Linking über onNotificationOpenedApp und getInitialNotification aus einem bloßen Hinweis einen echten Einstiegspunkt in die App macht. Topics decken breite Broadcast-Szenarien ab, für feinere Segmentierung braucht es eine eigene Token-Datenbank oder einen spezialisierten Dienst wie OneSignal. Wer diese Bausteine sauber zusammensetzt, bekommt Push Notifications, die in der Praxis tatsächlich ankommen, statt nur in der Theorie zugestellt zu werden.
Push Notifications mit Firebase Cloud Messaging, das Wichtigste auf einen Blick
Zwei Zustellsysteme
APNs für iOS, FCM für Android. FCM reicht iOS-Nachrichten lediglich an APNs weiter, statt sie direkt zuzustellen.
Drei App-Zustände
onMessage, setBackgroundMessageHandler und getInitialNotification decken Foreground, Background und Quit State ab.
Berechtigungen zeitkritisch
iOS: requestPermission() explizit anfragen. Android 13+: POST_NOTIFICATIONS als Runtime-Permission zwingend.
Token- und Topic-Pflege
onTokenRefresh registrieren, ungültige Tokens entfernen, Topics für Broadcasts, eigene Datenbank für Segmentierung.