Nachrichten in einer Flutter-App empfangen

Je nach Gerätestatus werden eingehende Nachrichten unterschiedlich behandelt. Bis diese Szenarien zu verstehen und FCM in Ihre eigene Anwendung zu integrieren, ist zunächst wichtig, die verschiedenen Stadien festzulegen, in denen sich ein Gerät befinden kann:

Status Beschreibung
Vordergrund Die App ist geöffnet, sichtbar und wird verwendet.
Hintergrund Die Anwendung ist geöffnet, aber im Hintergrund (minimiert). Dies ist in der Regel der Fall, wenn die Nutzenden Schaltfläche auf dem Gerät über den App-Schnellzugriff zu einer anderen App gewechselt ist, oder die Anwendung in einem anderen Tab geöffnet hat (Web).
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 Nachrichtennutzlasten über FCM empfangen:

  • 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 auf Android-Geräten das Beenden der App über die Geräteeinstellungen erzwingt, muss sie manuell wieder geöffnet werden, damit Nachrichten funktionieren.
  • Im Web müssen Sie mit getToken() ein Token mit Ihrem Web Push Certificate angefordert haben.

Berechtigung zum Empfangen von Nachrichten anfordern

Unter iOS, macOS, Web und Android 13 (oder höher), bevor FCM-Nutzlasten verarbeitet werden können auf Ihrem Gerät empfangen werden, müssen Sie zuerst den Nutzer um Erlaubnis fragen.

Das Paket firebase_messaging bietet eine einfache API zum Anfordern einer Berechtigung über die Methode requestPermission. Diese API akzeptiert eine Reihe benannter Argumente, mit denen die Art der angeforderten Berechtigungen definiert wird, z. B. können Nachrichten mit Nutzlasten von Benachrichtigungen einen Ton auslösen oder Nachrichten über Siri vorlesen. Standardmäßig fordert die Methode sinnvolle Standardberechtigungen an. In der Referenz-API finden Sie eine vollständige Dokumentation darüber, wofür die einzelnen Berechtigungen gedacht sind.

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 verweigert.
  • notDetermined: Der Nutzer hat noch nicht entschieden, ob er eine Berechtigung erteilen möchte.
  • provisional: Der Nutzer hat eine vorläufige Berechtigung erteilt.

Die anderen Attribute von NotificationSettings geben an, ob eine bestimmte Berechtigung für die aktuelle Version aktiviert, deaktiviert oder nicht unterstützt wird .

Sobald die Berechtigung erteilt wurde und die verschiedenen Gerätestatus verstanden wurden, kann Ihre App die eingehenden Anfragen verarbeiten. FCM-Nutzlasten.

Nachrichtenverarbeitung

Je nach aktuellem Status Ihrer Anwendung werden eingehende Nutzlasten Nachrichtentypen erfordern verschiedene 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 einen RemoteMessage mit Details zu verschiedene Informationen zur Nutzlast, wie Herkunft, eindeutige ID, Sendezeit, eine Benachrichtigung und mehr. Da die Nachricht abgerufen wurde, während die Anwendung im Vordergrund ausgeführt wird, können Sie direkt auf Ihre Flutter- Status und Kontext der Anwendung.

Vordergrund- und Benachrichtigungsnachrichten

Benachrichtigungen, die eingehen, während die Anwendung im Vordergrund ausgeführt wird, zeigen standardmäßig keine sichtbare Benachrichtigung an. Android und iOS. Es ist jedoch möglich, dieses Verhalten außer Kraft zu setzen:

  • 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

Hintergrundnachrichten werden bei nativen Android- und Apple) und webbasierte Plattformen.

Apple-Plattformen und Android

Registrieren Sie einen onBackgroundMessage-Handler, um Hintergrundnachrichten zu verarbeiten. Beim Empfang von Nachrichten wird ein "Isola" wird erzeugt (nur Android, iOS/macOS erfordert keine separate Isolierung), sodass Sie Nachrichten auch dann verarbeiten können, 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 Version 3.3.0 oder höher verwenden, muss der Nachrichten-Handler direkt über der Funktionsdeklaration mit @pragma('vm:entry-point') gekennzeichnet werden. Andernfalls kann er im Release-Modus während der Baumschüttelung entfernt werden.
@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 einer eigenen Isolierung außerhalb des Anwendungskontexts ausgeführt wird, kann der oder die Benutzeroberfläche ausführen, die die Logik beeinflusst. Sie können jedoch Logik wie HTTP-Anfragen, E/A-Vorgänge ausführen. (z.B. Aktualisieren des lokalen Speichers), Kommunikation mit anderen Plug-ins usw.

Es wird außerdem empfohlen, 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 Aufgaben länger als 30 Sekunden dauern, 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 Service 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 Worker registriert werden. Registrieren Sie den Worker in der Datei index.html, indem Sie das <script>-Tag ändern, mit dem Flutter gestartet wird:

<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 Vorlagensystem verwenden, können Sie den Worker registrieren, indem Sie das <script>-Tag ändern, das Flutter so 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.

Umgang mit Interaktionen

Da Benachrichtigungen ein sichtbarer Hinweis sind, interagieren Nutzer normalerweise mit ihnen (durch Drücken). Sowohl auf Android- als auch auf iOS-Geräten wird standardmäßig die . 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 möchten Sie möglicherweise die Interaktion des Nutzers beim Öffnen der Anwendung verarbeiten. Wenn zum Beispiel eine neue Chatnachricht über eine Benachrichtigung erhalten und der Nutzer darauf drückt, möchten Sie möglicherweise die entsprechende Konversation öffnen, wenn die Anwendung geöffnet wird.

Das Paket firebase-messaging 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 RemoteMessage entfernt.
  • onMessageOpenedApp: Ein Stream, das eine RemoteMessage postet, wenn die Anwendung aus einem Hintergrundstatus geöffnet wird.

Es wird empfohlen, beide Szenarien zu berücksichtigen, um eine reibungslose User Experience zu gewährleisten. Das folgende Codebeispiel zeigt, wie dies erreicht werden kann:

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 die Interaktion handhaben, hängt von der Einrichtung Ihrer Anwendung ab. Das obige Beispiel zeigt eine einfache Darstellung mit einem StatefulWidget.

Nachrichten lokalisieren

Es gibt zwei Möglichkeiten, lokalisierte Strings zu senden:

  • Sie können die bevorzugte Sprache jedes Nutzers auf Ihrem Server speichern und benutzerdefinierte Benachrichtigungen für jede Sprache senden.
  • Betten Sie lokalisierte Strings in Ihre App ein und nutzen Sie die Spracheinstellungen des Betriebssystems.

So verwenden Sie die zweite Methode:

Android

  1. Geben Sie Ihre Nachrichten in der Standardsprache auf resources/values/strings.xml 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. 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. Verwenden Sie in der Servernutzlast statt der Schlüssel title, message und body die Schlüssel title_loc_key und body_loc_key für Ihre lokalisierte Nachricht und stellen Sie sie auf das Attribut name ein, das der Nachricht 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 Ihre Nachrichten in der Standardsprache auf Base.lproj/Localizable.strings an:

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

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

    Die Nachrichtennutzlast würde so aussehen:

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

Datenexport für die Nachrichtenzustellung 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. Export nach BigQuery enthält alle verfügbaren Daten für Nachrichten, unabhängig vom Nachrichtentyp oder davon, ob die Nachricht über der API oder dem Notifications Composer.

Um den Export zu aktivieren, führen Sie zuerst die hier beschriebenen Schritte aus. Folgen Sie dann dieser Anleitung:

Android

Sie können den folgenden Code verwenden:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

iOS

Für iOS musst du AppDelegate.m mit dem folgenden Inhalt ändern.

#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 Version 9 des SDKs verwenden zu können. Version 9 muss gebündelt werden, daher müssen Sie beispielsweise einen Bundler wie esbuild verwenden um den Service Worker zur Arbeit zu bringen. Sehen Sie sich die Beispiel-App an, um zu sehen, wie Sie dazu vorgehen.

Nach der Migration zum SDK v9 können Sie 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-Datei den Firebase Auth-Pod hinzufügen.

Schritt 1: Benachrichtigungsdiensterweiterung hinzufügen

  1. Klicken Sie in Xcode auf File > 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 die Schritte in dieser Anleitung auszuführen), 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

Sorgen Sie dafür, dass die neue Erweiterung Zugriff auf den Pod Firebase/Messaging hat. Fügen Sie ihn dazu in die Podfile-Datei ein:

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

  2. Scrollen Sie zum Ende der Datei 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: Erweiterungshilfe verwenden

Jetzt sollte alles noch normal funktionieren. Im letzten Schritt rufen Sie den Erweiterungsassistenten auf.

  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 gezeigt.

    Ersetzen Sie den Inhalt von NotificationService.m durch:

    #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. Informationen zum Erstellen einer Sendeanfrage finden Sie in der iOS-Dokumentation. Beachten Sie, dass das Gerät eine maximale Bildgröße von 300 KB durchsetzt.