Nachrichten in einer Flutter-App empfangen

Je nach Gerätestatus werden eingehende Nachrichten unterschiedlich behandelt. Um diese Szenarien zu verstehen und FCM in Ihre eigene Anwendung einzubinden, ist es wichtig, die verschiedenen Status zu kennen, in denen sich ein Gerät befinden kann:

Status Beschreibung
Vordergrund Wenn die Anwendung geöffnet, sichtbar und in Verwendung ist.
Hintergrund Wenn die Anwendung geöffnet, aber im Hintergrund (minimiert) ist. Das tritt in der Regel auf, wenn der Nutzer die Startbildschirmtaste auf dem Gerät gedrückt, über den App-Schnellzugriff zu einer anderen App gewechselt oder die Anwendung in einem anderen Tab (Web) geöffnet hat.
Beendet Wenn das Gerät gesperrt ist oder die App nicht ausgeführt wird.

Es gibt einige Voraussetzungen, die erfüllt sein müssen, damit die Anwendung Nachrichtenn-Nutzlast über FCM empfangen kann:

  • Die App muss mindestens einmal geöffnet worden sein, damit eine Registrierung bei FCM möglich ist.
  • Wenn der Nutzer unter iOS die App aus dem App-Schnellzugriff wischt, muss sie manuell wieder geöffnet werden, damit Hintergrundnachrichten wieder funktionieren.
  • Wenn der Nutzer die App unter Android über die Geräteeinstellungen beendet, muss sie manuell wieder geöffnet werden, damit Nachrichten funktionieren.
  • Im Web müssen Sie mit Ihrem Web-Push-Zertifikat ein Token (mit getToken()) angefordert haben.

Berechtigung zum Empfangen von Nachrichten anfordern

Unter iOS, macOS, im Web und unter Android 13 (oder höher) müssen Sie die Erlaubnis des Nutzers einholen, bevor FCM-Nutzlast auf Ihrem Gerät empfangen werden kann.

Das firebase_messaging-Paket bietet eine einfache API, um über die Methode requestPermission eine Berechtigung anzufordern. Diese API akzeptiert eine Reihe von benannten Argumenten, die die Art der Berechtigungen definieren, die Sie anfordern möchten. So können Sie beispielsweise festlegen, ob Nachrichten mit Benachrichtigungsnutzlasten einen Ton auslösen oder Nachrichten über Siri vorlesen können. Standardmäßig werden mit der Methode sensible Standardberechtigungen angefordert. Die Referenz-API enthält eine vollständige Dokumentation zu den einzelnen Berechtigungen.

Rufen Sie zuerst die Methode aus Ihrer Anwendung auf. Auf iOS-Geräten wird ein natives Modal angezeigt, im Web wird der native API-Vorgang des Browsers ausgelöst:

FirebaseMessaging messaging = FirebaseMessaging.instance;

NotificationSettings settings = await messaging.requestPermission(
  alert: true,
  announcement: false,
  badge: true,
  carPlay: false,
  criticalAlert: false,
  provisional: false,
  sound: true,
);

print('User granted permission: ${settings.authorizationStatus}');

Anhand der authorizationStatus-Eigenschaft des NotificationSettings-Objekts, das von der Anfrage zurückgegeben wird, lässt sich die Entscheidung des Nutzers insgesamt ermitteln:

  • authorized: Der Nutzer hat die Berechtigung erteilt.
  • denied: Der Nutzer hat die Berechtigung abgelehnt.
  • notDetermined: Der Nutzer hat noch nicht entschieden, ob er die Berechtigung gewähren möchte.
  • provisional: Der Nutzer hat eine vorläufige Berechtigung erteilt

Die anderen Properties unter NotificationSettings geben an, ob eine bestimmte Berechtigung auf dem aktuellen Gerät aktiviert, deaktiviert oder nicht unterstützt wird.

Sobald die Berechtigung erteilt wurde und die verschiedenen Gerätestatus bekannt sind, kann Ihre Anwendung mit der Verarbeitung der eingehenden FCM-Nutzlast beginnen.

Nachrichtenverarbeitung

Je nach aktuellem Status Ihrer Anwendung erfordern eingehende Nutzlasten verschiedener Nachrichtentypen unterschiedliche Implementierungen:

Nachrichten im Vordergrund

Wenn Sie Nachrichten verarbeiten möchten, während Ihre Anwendung im Vordergrund ausgeführt wird, hören Sie sich den onMessage-Stream an.

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}');
  }
});

Der Stream enthält eine RemoteMessage mit verschiedenen Informationen zur Nutzlast, z. B. Herkunft, eindeutige ID, gesendete Uhrzeit und ob eine Benachrichtigung enthalten war. Da die Nachricht abgerufen wurde, während sich Ihre Anwendung im Vordergrund befindet, können Sie direkt auf den Status und den Kontext Ihrer Flutter-Anwendung zugreifen.

Nachrichten im Vordergrund und Benachrichtigungen

Benachrichtigungen, die eintreffen, während die App im Vordergrund ausgeführt wird, werden sowohl unter Android als auch unter iOS standardmäßig nicht angezeigt. Sie können dieses Verhalten jedoch überschreiben:

  • Unter Android müssen Sie einen Benachrichtigungskanal mit der Priorität „Hoch“ erstellen.
  • Unter iOS können Sie die Präsentationsoptionen für die Anwendung aktualisieren.

Hintergrundnachrichten

Die Verarbeitung von Hintergrundnachrichten unterscheidet sich auf nativen (Android und Apple) und webbasierten Plattformen.

Apple-Plattformen und Android

Registrieren Sie einen onBackgroundMessage-Handler, um Hintergrundnachrichten zu verarbeiten. Wenn Nachrichten empfangen werden, wird ein Isolate erstellt (nur Android, iOS/macOS erfordern kein separates Isolate). So können Sie Nachrichten auch dann verarbeiten, wenn Ihre Anwendung nicht ausgeführt wird.

Beachten Sie bei Ihrem Handler für Hintergrundnachrichten Folgendes:

  1. Es darf sich nicht um eine anonyme Funktion handeln.
  2. Es muss sich um eine Funktion der obersten Ebene handeln (z.B. keine Klassenmethode, die eine Initialisierung erfordert).
  3. Wenn Sie Flutter 3.3.0 oder höher verwenden, muss der Nachrichtenhandler direkt über der Funktionsdeklaration mit @pragma('vm:entry-point') annotiert werden. Andernfalls wird er beim Tree Shaking für den Release-Modus möglicherweise entfernt.
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // If you're going to use other Firebase services in the background, such as Firestore,
  // make sure you call `initializeApp` before using other Firebase services.
  await Firebase.initializeApp();

  print("Handling a background message: ${message.messageId}");
}

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

Da der Handler in einem eigenen Isolate außerhalb des Anwendungskontexts ausgeführt wird, ist es nicht möglich, den Anwendungsstatus zu aktualisieren oder Logik auszuführen, die sich auf die Benutzeroberfläche auswirkt. Sie können jedoch Logik wie HTTP-Anfragen ausführen, E/A-Vorgänge ausführen (z.B. lokalen Speicher aktualisieren) und mit anderen Plug-ins kommunizieren.

Wir empfehlen Ihnen außerdem, die Logik so schnell wie möglich fertigzustellen. Das Ausführen langer, intensiver Aufgaben wirkt sich auf die Geräteleistung aus und kann dazu führen, dass das Betriebssystem den Prozess beendet. Wenn Tasks länger als 30 Sekunden ausgeführt werden, wird der Prozess möglicherweise automatisch vom Gerät beendet.

Web

Erstellen Sie im Web einen JavaScript-Dienst-Worker, der im Hintergrund ausgeführt wird. Verwenden Sie den Dienst-Worker, um Hintergrundnachrichten zu verarbeiten.

Erstellen Sie zuerst eine neue Datei im Verzeichnis web und nennen Sie sie firebase-messaging-sw.js:

// Please see this file for the latest firebase-js-sdk version:
// https://github.com/firebase/flutterfire/blob/master/packages/firebase_core/firebase_core_web/lib/src/firebase_sdk_version.dart
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-app-compat.js");
importScripts("https://www.gstatic.com/firebasejs/10.7.0/firebase-messaging-compat.js");

firebase.initializeApp({
  apiKey: "...",
  authDomain: "...",
  databaseURL: "...",
  projectId: "...",
  storageBucket: "...",
  messagingSenderId: "...",
  appId: "...",
});

const messaging = firebase.messaging();

// Optional:
messaging.onBackgroundMessage((message) => {
  console.log("onBackgroundMessage", message);
});

Die Datei muss sowohl das App- als auch das Messaging-SDK importieren, Firebase initialisieren und die Variable messaging freigeben.

Als Nächstes muss der Mitarbeiter registriert werden. Registrieren Sie den Worker in der Datei index.html, indem Sie das <script>-Tag ändern, das Flutter startet:

<script src="flutter_bootstrap.js" async>
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
      navigator.serviceWorker.register('firebase-messaging-sw.js', {
        scope: '/firebase-cloud-messaging-push-scope',
      });
    });
  }
</script>

Wenn Sie noch das alte templating-System verwenden, können Sie den Worker registrieren, indem Sie das <script>-Tag ändern, das Flutter startet:

<html>
<body>
  <script>
      var serviceWorkerVersion = null;
      var scriptLoaded = false;
      function loadMainDartJs() {
        if (scriptLoaded) {
          return;
        }
        scriptLoaded = true;
        var scriptTag = document.createElement('script');
        scriptTag.src = 'main.dart.js';
        scriptTag.type = 'application/javascript';
        document.body.append(scriptTag);
      }

      if ('serviceWorker' in navigator) {
        // Service workers are supported. Use them.
        window.addEventListener('load', function () {
          // Register Firebase Messaging service worker.
          navigator.serviceWorker.register('firebase-messaging-sw.js', {
            scope: '/firebase-cloud-messaging-push-scope',
          });

          // Wait for registration to finish before dropping the <script> tag.
          // Otherwise, the browser will load the script multiple times,
          // potentially different versions.
          var serviceWorkerUrl =
            'flutter_service_worker.js?v=' + serviceWorkerVersion;

          navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
            function waitForActivation(serviceWorker) {
              serviceWorker.addEventListener('statechange', () => {
                if (serviceWorker.state == 'activated') {
                  console.log('Installed new service worker.');
                  loadMainDartJs();
                }
              });
            }
            if (!reg.active && (reg.installing || reg.waiting)) {
              // No active web worker and we have installed or are installing
              // one for the first time. Simply wait for it to activate.
              waitForActivation(reg.installing ?? reg.waiting);
            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
              // When the app updates the serviceWorkerVersion changes, so we
              // need to ask the service worker to update.
              console.log('New service worker available.');
              reg.update();
              waitForActivation(reg.installing);
            } else {
              // Existing service worker is still good.
              console.log('Loading app from service worker.');
              loadMainDartJs();
            }
          });

          // If service worker doesn't succeed in a reasonable amount of time,
          // fallback to plaint <script> tag.
          setTimeout(() => {
            if (!scriptLoaded) {
              console.warn(
                'Failed to load app from service worker. Falling back to plain <script> tag.'
              );
              loadMainDartJs();
            }
          }, 4000);
        });
      } else {
        // Service workers not supported. Just drop the <script> tag.
        loadMainDartJs();
      }
  </script>
</body>

Starten Sie als Nächstes Ihre Flutter-Anwendung neu. Der Worker wird registriert und alle Hintergrundnachrichten werden über diese Datei verarbeitet.

Interaktionen verarbeiten

Da Benachrichtigungen ein sichtbarer Hinweis sind, ist es üblich, dass Nutzer mit ihnen interagieren (indem sie darauf tippen). Sowohl unter Android als auch unter iOS wird standardmäßig die App geöffnet. Wenn die Anwendung beendet wurde, wird sie gestartet. Wenn sie sich im Hintergrund befindet, wird sie in den Vordergrund gebracht.

Je nach Inhalt einer Benachrichtigung können Sie die Interaktion des Nutzers beim Öffnen der Anwendung steuern. Wenn beispielsweise eine neue Chatnachricht über eine Benachrichtigung gesendet wird und der Nutzer darauf klickt, können Sie die entsprechende Unterhaltung öffnen, wenn die Anwendung geöffnet wird.

Das firebase-messaging-Paket bietet zwei Möglichkeiten, diese Interaktion zu verarbeiten:

  • getInitialMessage(): Wenn die Anwendung aus einem beendeten Zustand geöffnet wird, wird eine Future mit einer RemoteMessage zurückgegeben. Nach dem Verbrauch wird die RemoteMessage entfernt.
  • onMessageOpenedApp: Ein Stream, das eine RemoteMessage postet, wenn die Anwendung aus einem Hintergrundstatus geöffnet wird.

Wir empfehlen, beide Szenarien zu behandeln, um Nutzern eine reibungslose Nutzung zu ermöglichen. Im folgenden Codebeispiel wird gezeigt, wie das geht:

class Application extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _Application();
}

class _Application extends State<Application> {
  // It is assumed that all messages contain a data field with the key 'type'
  Future<void> setupInteractedMessage() async {
    // Get any messages which caused the application to open from
    // a terminated state.
    RemoteMessage? initialMessage =
        await FirebaseMessaging.instance.getInitialMessage();

    // If the message also contains a data property with a "type" of "chat",
    // navigate to a chat screen
    if (initialMessage != null) {
      _handleMessage(initialMessage);
    }

    // Also handle any interaction when the app is in the background via a
    // Stream listener
    FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
  }

  void _handleMessage(RemoteMessage message) {
    if (message.data['type'] == 'chat') {
      Navigator.pushNamed(context, '/chat',
        arguments: ChatArguments(message),
      );
    }
  }

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

    // Run code required to handle interacted messages in an async function
    // as initState() must not be async
    setupInteractedMessage();
  }

  @override
  Widget build(BuildContext context) {
    return Text("...");
  }
}

Wie Sie mit Interaktionen umgehen, hängt von der Konfiguration Ihrer Anwendung ab. Das obige Beispiel zeigt eine einfache Abbildung mit einem StatefulWidget.

Nachrichten lokalisieren

Sie haben zwei Möglichkeiten, lokalisierte Strings zu senden:

  • Die bevorzugte Sprache jedes Nutzers auf Ihrem Server speichern und personalisierte Benachrichtigungen für jede Sprache senden
  • Lokalisierte Strings in Ihre App einbetten und die lokalen Einstellungen des Betriebssystems verwenden

So verwenden Sie die zweite Methode:

Android

  1. Geben Sie in resources/values/strings.xml die Nachrichten in der Standardsprache an:

    <string name="notification_title">Hello world</string>
    <string name="notification_message">This is a message</string>
    
  2. Geben Sie die übersetzten Nachrichten im Verzeichnis values-language an. So geben Sie beispielsweise französische Nachrichten in resources/values-fr/strings.xml an:

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. Verwende in der Servernutzlast anstelle der Schlüssel title, message und body die Schlüssel title_loc_key und body_loc_key für die lokalisierte Nachricht und setze sie auf das name-Attribut der Nachricht, die angezeigt werden soll.

    Die Nachrichtenn-Nutzlast würde so aussehen:

    {
      "data": {
        "title_loc_key": "notification_title",
        "body_loc_key": "notification_message"
      }
    }
    

iOS

  1. Geben Sie in Base.lproj/Localizable.strings die Nachrichten in der Standardsprache an:

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Geben Sie die übersetzten Nachrichten im Verzeichnis language.lproj an. So geben Sie beispielsweise französische Meldungen in fr.lproj/Localizable.strings an:

    "NOTIFICATION_TITLE" = "Bonjour le monde";
    "NOTIFICATION_MESSAGE" = "C'est un message";
    

    Die Nachrichtenn-Nutzlast würde so aussehen:

    {
      "data": {
        "title_loc_key": "NOTIFICATION_TITLE",
        "body_loc_key": "NOTIFICATION_MESSAGE"
      }
    }
    

Export von Daten zur Nachrichtenübermittlung aktivieren

Sie können Ihre Nachrichtendaten zur weiteren Analyse in BigQuery exportieren. In BigQuery können Sie die Daten mit BigQuery SQL analysieren, in einen anderen Cloud-Anbieter exportieren oder für Ihre benutzerdefinierten ML-Modelle verwenden. Ein Export nach BigQuery enthält alle verfügbaren Daten für Nachrichten, unabhängig vom Nachrichtentyp oder davon, ob die Nachricht über die API oder den Benachrichtigungs-Editor gesendet wird.

Wenn Sie den Export aktivieren möchten, führen Sie zuerst die hier beschriebenen Schritte aus und gehen Sie dann so vor:

Android

Sie können den folgenden Code verwenden:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

iOS

Für iOS müssen Sie AppDelegate.m durch den folgenden Inhalt ersetzen.

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import <Firebase/Firebase.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)userInfo
          fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  [[FIRMessaging extensionHelper] exportDeliveryMetricsToBigQueryWithMessageInfo:userInfo];
}

@end

Web

Für das Web müssen Sie Ihren Service Worker ändern, um die Version 9 des SDKs zu verwenden. Die Version 9 muss gebundelt werden. Sie müssen also einen Bundler wie esbuild verwenden, damit der Service Worker funktioniert. In der Beispiel-App sehen Sie, wie das geht.

Nachdem du zur Version 9 des SDK migriert bist, kannst du den folgenden Code verwenden:

import {
  experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
  getMessaging,
} from 'firebase/messaging/sw';
...

const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);

Vergessen Sie nicht, yarn build auszuführen, um die neue Version Ihres Service Workers in den Ordner web zu exportieren.

Bilder in Benachrichtigungen auf iOS-Geräten anzeigen

Damit auf Apple-Geräten eingehende FCM-Benachrichtigungen Bilder aus der FCM-Nutzlast anzeigen, müssen Sie eine zusätzliche Benachrichtigungsdiensterweiterung hinzufügen und Ihre App so konfigurieren, dass sie verwendet wird.

Wenn Sie die Firebase-Telefonauthentifizierung verwenden, müssen Sie Ihrer Podfile den Firebase Auth-Pod hinzufügen.

Schritt 1: Erweiterung für Benachrichtigungsdienst hinzufügen

  1. Klicken Sie in Xcode auf File > New > Target… (Datei > Neu > Ziel…).
  2. In einem modalen Dialogfeld wird eine Liste möglicher Ziele angezeigt. Scrollen Sie nach unten oder verwenden Sie den Filter, um Notification Service Extension auszuwählen. Klicken Sie auf Next.
  3. Fügen Sie einen Produktnamen hinzu (verwenden Sie „ImageNotification“, um dieser Anleitung zu folgen), legen Sie die Sprache auf „Objective-C“ fest und klicken Sie auf Fertigstellen.
  4. Klicken Sie auf Aktivieren, um das Schema zu aktivieren.

Schritt 2: Ziel zur Podfile hinzufügen

Achten Sie darauf, dass Ihre neue Erweiterung Zugriff auf den Firebase/Messaging-Pod hat, indem Sie sie in die Podfile einfügen:

  1. Öffnen Sie die Podfile-Datei im Navigationsbereich: Pods > Podfile.

  2. Scrollen Sie nach unten und fügen Sie Folgendes hinzu:

    target 'ImageNotification' do
      use_frameworks!
      pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication
      pod 'Firebase/Messaging'
    end
    
  3. Installieren oder aktualisieren Sie Ihre Pods mit pod install aus dem Verzeichnis ios oder macos.

Schritt 3: Erweiterungsassistent verwenden

Bis dahin sollte alles wie gewohnt funktionieren. Im letzten Schritt wird der Erweiterungs-Helper aufgerufen.

  1. Wählen Sie im Navigationsbereich die Erweiterung „ImageNotification“ aus.

  2. Öffnen Sie die Datei NotificationService.m.

  3. Importieren Sie oben in der Datei FirebaseMessaging.h direkt nach NotificationService.h, wie unten dargestellt.

    Ersetzen Sie den Inhalt von NotificationService.m durch Folgendes:

    #import "NotificationService.h"
    #import "FirebaseMessaging.h"
    #import "FirebaseAuth.h" // Add this line if you are using FirebaseAuth phone authentication
    #import <UIKit/UIKit.h> // Add this line if you are using FirebaseAuth phone authentication
    
    @interface NotificationService ()
    
    @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
    @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
    
    @end
    
    @implementation NotificationService
    
    /* Uncomment this if you are using Firebase Auth
    - (BOOL)application:(UIApplication *)app
                openURL:(NSURL *)url
                options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
      if ([[FIRAuth auth] canHandleURL:url]) {
        return YES;
      }
      return NO;
    }
    
    - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
      for (UIOpenURLContext *urlContext in URLContexts) {
        [FIRAuth.auth canHandleURL:urlContext.URL];
      }
    }
    */
    
    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
        self.contentHandler = contentHandler;
        self.bestAttemptContent = [request.content mutableCopy];
    
        // Modify the notification content here...
        [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler];
    }
    
    - (void)serviceExtensionTimeWillExpire {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        self.contentHandler(self.bestAttemptContent);
    }
    
    @end
    

Schritt 4: Bild zur Nutzlast hinzufügen

Sie können Ihrer Benachrichtigungsnutzlast jetzt ein Bild hinzufügen. Weitere Informationen zum Erstellen einer Sendeanfrage finden Sie in der iOS-Dokumentation unter Sendeanfrage erstellen. Die maximale Bildgröße beträgt 300 KB.