Odbieraj wiadomości w aplikacji Flutter

W zależności od stanu urządzenia wiadomości przychodzące są obsługiwane w różny sposób. Aby zrozumieć te scenariusze i sposób integracji FCM z własną aplikacją, ważne jest najpierw ustalenie różnych stanów, w jakich może znajdować się urządzenie:

Państwo Opis
Pierwszoplanowy Kiedy aplikacja jest otwarta, widoczna i używana.
Tło Gdy aplikacja jest otwarta, ale w tle (zminimalizowana). Zwykle dzieje się tak, gdy użytkownik naciśnie przycisk „home” na urządzeniu, przełączy się do innej aplikacji za pomocą przełącznika aplikacji lub otworzy aplikację na innej karcie (w internecie).
Zakończony Gdy urządzenie jest zablokowane lub aplikacja nie jest uruchomiona.

Istnieje kilka warunków wstępnych, które muszą zostać spełnione, zanim aplikacja będzie mogła odbierać ładunki komunikatów za pośrednictwem FCM:

  • Aplikacja musi zostać otwarta przynajmniej raz (aby możliwa była rejestracja w FCM).
  • Jeśli w systemie iOS użytkownik przesunie aplikację z przełącznika aplikacji, należy ją ręcznie otworzyć ponownie, aby wiadomości w tle znów zaczęły działać.
  • Jeśli w systemie Android użytkownik wymusi zamknięcie aplikacji w ustawieniach urządzenia, należy ją ręcznie otworzyć ponownie, aby wiadomości zaczęły działać.
  • W Internecie musisz poprosić o token (używając getToken() ) wraz ze swoim certyfikatem web push.

Poproś o pozwolenie na odbieranie wiadomości

W systemach iOS, macOS, web i Android 13 (lub nowszych), zanim ładunki FCM będą mogły zostać odebrane na Twoim urządzeniu, musisz najpierw zapytać użytkownika o pozwolenie.

Pakiet firebase_messaging zapewnia prosty interfejs API umożliwiający żądanie pozwolenia za pomocą metody requestPermission . Ten interfejs API akceptuje wiele nazwanych argumentów, które definiują typ uprawnień, o które chcesz poprosić, na przykład to, czy wiadomości zawierające powiadomienia mogą wyzwalać dźwięk lub odczytywać wiadomości za pośrednictwem Siri. Domyślnie metoda żąda rozsądnych uprawnień domyślnych. Referencyjny interfejs API zapewnia pełną dokumentację dotyczącą przeznaczenia poszczególnych uprawnień.

Aby rozpocząć, wywołaj metodę ze swojej aplikacji (w systemie iOS zostanie wyświetlony natywny modal, w przeglądarce zostanie uruchomiony natywny przepływ API przeglądarki):

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

authorizationStatus obiektu NotificationSettings zwróconego z żądania może zostać wykorzystana do określenia ogólnej decyzji użytkownika:

  • authorized : użytkownik udzielił pozwolenia.
  • denied : użytkownik odmówił pozwolenia.
  • notDetermined : Użytkownik nie zdecydował jeszcze, czy udzielić pozwolenia.
  • provisional : użytkownik udzielił tymczasowego pozwolenia

Inne właściwości NotificationSettings zwracają informację, czy określone uprawnienie jest włączone, wyłączone lub nieobsługiwane na bieżącym urządzeniu.

Po udzieleniu pozwolenia i poznaniu różnych typów stanu urządzenia aplikacja może teraz zacząć obsługiwać przychodzące ładunki FCM.

Obsługa wiadomości

W zależności od bieżącego stanu aplikacji przychodzące ładunki różnych typów komunikatów wymagają różnych implementacji do ich obsługi:

Wiadomości na pierwszym planie

Aby obsługiwać wiadomości, gdy aplikacja jest na pierwszym planie, odsłuchaj strumień onMessage .

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

Strumień zawiera komunikat RemoteMessage zawierający szczegółowe informacje na temat ładunku, takie jak jego pochodzenie, unikalny identyfikator, czas wysłania, informacja o tym, czy zawierał powiadomienie i inne. Ponieważ wiadomość została pobrana, gdy aplikacja była na pierwszym planie, możesz bezpośrednio uzyskać dostęp do stanu i kontekstu aplikacji Flutter.

Wiadomości na pierwszym planie i powiadomienia

Powiadomienia, które przychodzą, gdy aplikacja jest na pierwszym planie, domyślnie nie wyświetlają widocznego powiadomienia, zarówno w systemie Android, jak i iOS. Możliwe jest jednak obejście tego zachowania:

  • W systemie Android musisz utworzyć kanał powiadomień o „wysokim priorytecie”.
  • W systemie iOS możesz zaktualizować opcje prezentacji aplikacji.

Wiadomości w tle

Proces obsługi komunikatów w tle jest inny na platformach natywnych (Android i Apple) i na platformach internetowych.

Platformy Apple i Android

Obsługuj komunikaty w tle, rejestrując procedurę obsługi onBackgroundMessage . Po odebraniu wiadomości tworzony jest izolat (tylko w systemie Android, iOS/macOS nie wymaga osobnego izolowania), co pozwala na obsługę wiadomości nawet wtedy, gdy aplikacja nie jest uruchomiona.

Jest kilka rzeczy, o których warto pamiętać na temat procedury obsługi wiadomości w tle:

  1. Nie może to być funkcja anonimowa.
  2. Musi to być funkcja najwyższego poziomu (np. nie metoda klasowa wymagająca inicjalizacji).
  3. Podczas korzystania z Flutter w wersji 3.3.0 lub wyższej, procedura obsługi wiadomości musi być opatrzona adnotacją @pragma('vm:entry-point') tuż nad deklaracją funkcji (w przeciwnym razie może zostać usunięta podczas potrząsania drzewem w trybie zwolnienia).
@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());
}

Ponieważ moduł obsługi działa samodzielnie poza kontekstem aplikacji, nie jest możliwa aktualizacja stanu aplikacji ani wykonywanie jakiejkolwiek logiki wpływającej na interfejs użytkownika. Możesz jednak wykonywać operacje logiczne, takie jak żądania HTTP, wykonywać operacje IO (np. aktualizować pamięć lokalną), komunikować się z innymi wtyczkami itp.

Zalecane jest również jak najszybsze uzupełnienie logiki. Uruchamianie długich i intensywnych zadań wpływa na wydajność urządzenia i może spowodować zakończenie procesu przez system operacyjny. Jeśli zadania trwają dłużej niż 30 sekund, urządzenie może automatycznie zakończyć proces.

Sieć

W Internecie napisz Service Worker JavaScript, który będzie działał w tle. Użyj Service Workera do obsługi komunikatów w tle.

Aby rozpocząć, utwórz nowy plik w swoim katalogu web i nazwij go firebase-messaging-sw.js :

importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js");

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

const messaging = firebase.messaging();

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

Plik musi zaimportować zarówno aplikację, jak i zestawy SDK do przesyłania wiadomości, zainicjować Firebase i udostępnić zmienną messaging .

Następnie pracownik musi zostać zarejestrowany. W pliku wejściowym, po załadowaniu pliku main.dart.js , zarejestruj swojego pracownika:

<html>
<body>
  ...
  <script src="main.dart.js" type="application/javascript"></script>
  <script>
       if ('serviceWorker' in navigator) {
          // Service workers are supported. Use them.
          window.addEventListener('load', function () {
            // ADD THIS LINE
            navigator.serviceWorker.register('/firebase-messaging-sw.js');

            // 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;

            //  ...
          });
      }
  </script>

Następnie uruchom ponownie aplikację Flutter. Pracownik zostanie zarejestrowany, a wszelkie wiadomości w tle będą obsługiwane za pośrednictwem tego pliku.

Obsługa interakcji

Ponieważ powiadomienia są widoczną wskazówką, użytkownicy często wchodzą z nimi w interakcję (poprzez naciśnięcie). Domyślnym zachowaniem zarówno na Androidzie, jak i iOS jest otwarcie aplikacji. Jeśli aplikacja zostanie zakończona, zostanie uruchomiona; jeśli jest w tle, zostanie przeniesiony na pierwszy plan.

W zależności od treści powiadomienia możesz chcieć obsłużyć interakcję użytkownika po otwarciu aplikacji. Na przykład, jeśli nowa wiadomość na czacie zostanie wysłana poprzez powiadomienie i użytkownik ją naciśnie, możesz chcieć otworzyć konkretną rozmowę po otwarciu aplikacji.

Pakiet firebase-messaging udostępnia dwa sposoby obsługi tej interakcji:

  • getInitialMessage() : Jeśli aplikacja zostanie otwarta ze stanu zakończonego, zwrócona zostanie Future zawierająca RemoteMessage . Po wykorzystaniu RemoteMessage zostanie usunięta.
  • onMessageOpenedApp : Stream , który publikuje komunikat RemoteMessage , gdy aplikacja jest otwierana ze stanu tła.

Zaleca się obsługę obu scenariuszy, aby zapewnić użytkownikom płynny UX. Poniższy przykład kodu opisuje, jak można to osiągnąć:

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("...");
  }
}

Sposób obsługi interakcji zależy od konfiguracji aplikacji. Powyższy przykład przedstawia podstawową ilustrację przy użyciu StatefulWidget.

Lokalizowanie wiadomości

Zlokalizowane ciągi znaków można wysyłać na dwa różne sposoby:

  • Przechowuj preferowany język każdego użytkownika na swoim serwerze i wysyłaj dostosowane powiadomienia dla każdego języka
  • Osadzaj zlokalizowane ciągi znaków w swojej aplikacji i korzystaj z natywnych ustawień regionalnych systemu operacyjnego

Oto jak skorzystać z drugiej metody:

Android

  1. Określ wiadomości w języku domyślnym w resources/values/strings.xml :

    <string name="notification_title">Hello world</string>
    <string name="notification_message">This is a message</string>
    
  2. Określ przetłumaczone wiadomości w katalogu values- language . Na przykład określ wiadomości w języku francuskim w resources/values-fr/strings.xml :

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. W ładunku serwera zamiast używać kluczy title , message i body , użyj title_loc_key i body_loc_key dla zlokalizowanej wiadomości i ustaw je na atrybut name wiadomości, którą chcesz wyświetlić.

    Ładunek wiadomości wyglądałby następująco:

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

iOS

  1. Określ wiadomości w języku domyślnym w Base.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Określ przetłumaczone wiadomości w katalogu language .lproj . Na przykład określ wiadomości w języku francuskim w fr.lproj/Localizable.strings :

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

    Ładunek wiadomości wyglądałby następująco:

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

Włącz eksport danych o dostarczeniu wiadomości

Możesz wyeksportować dane wiadomości do BigQuery w celu dalszej analizy. BigQuery pozwala analizować dane za pomocą BigQuery SQL, eksportować je do innego dostawcy usług w chmurze lub wykorzystywać dane do niestandardowych modeli uczenia maszynowego. Eksport do BigQuery obejmuje wszystkie dostępne dane wiadomości, niezależnie od typu wiadomości oraz tego, czy wiadomość jest wysyłana przez API czy kreator powiadomień.

Aby włączyć eksport, najpierw wykonaj kroki opisane tutaj , a następnie postępuj zgodnie z poniższymi instrukcjami:

Android

Możesz użyć następującego kodu:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

iOS

W przypadku systemu iOS należy zmienić plik AppDelegate.m na następującą treść.

#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

Sieć

W przypadku sieci Web należy zmienić pracownika usługi, aby móc korzystać z wersji 9 zestawu SDK. Wersja v9 musi być dołączona, więc musisz użyć programu pakującego, takiego jak na przykład esbuild , aby proces roboczy usługi mógł działać. Zobacz przykładową aplikację , aby zobaczyć, jak to osiągnąć.

Po migracji do zestawu SDK w wersji 9 możesz użyć następującego kodu:

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

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

Nie zapomnij uruchomić yarn build , aby wyeksportować nową wersję pracownika usług do folderu web .

Wyświetlaj obrazy w powiadomieniach na iOS

Aby na urządzeniach Apple przychodzące powiadomienia FCM wyświetlały obrazy z ładunku FCM, musisz dodać dodatkowe rozszerzenie usługi powiadomień i skonfigurować aplikację tak, aby z niego korzystała.

Jeśli korzystasz z uwierzytelniania telefonicznego Firebase, musisz dodać moduł Firebase Auth do swojego Podfile.

Krok 1 — Dodaj rozszerzenie usługi powiadomień

  1. W Xcode kliknij Plik > Nowy > Cel...
  2. Modal przedstawi listę możliwych celów; przewiń w dół lub użyj filtra, aby wybrać Rozszerzenie usługi powiadomień . Kliknij Następny .
  3. Dodaj nazwę produktu (użyj „ImageNotification”, aby postępować zgodnie z tym samouczkiem), ustaw język na Objective-C i kliknij Zakończ .
  4. Włącz schemat klikając Aktywuj .

Krok 2 – Dodaj cel do Podfile

Upewnij się, że Twoje nowe rozszerzenie ma dostęp do modułu Firebase/Messaging , dodając je w pliku Podfile:

  1. W Nawigatorze otwórz Podfile: Pody > Podfile

  2. Przewiń w dół do dołu pliku i dodaj:

    target 'ImageNotification' do
      use_frameworks!
      pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication
      pod 'Firebase/Messaging'
    end
    
  3. Zainstaluj lub zaktualizuj swoje pody, korzystając z pod install z katalogu ios lub macos .

Krok 3 — Użyj pomocnika rozszerzenia

W tym momencie wszystko powinno nadal działać normalnie. Ostatnim krokiem jest wywołanie pomocnika rozszerzenia.

  1. W nawigatorze wybierz rozszerzenie ImageNotification

  2. Otwórz plik NotificationService.m .

  3. Na górze pliku zaimportuj FirebaseMessaging.h zaraz po NotificationService.h , jak pokazano poniżej.

    Zamień zawartość NotificationService.m na:

    #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
    

Krok 4 — Dodaj obraz do ładunku

W ładunku powiadomienia możesz teraz dodać obraz. Zobacz dokumentację iOS dotyczącą tworzenia żądania wysłania . Należy pamiętać, że urządzenie wymusza maksymalny rozmiar obrazu wynoszący 300 KB.