Odbieranie wiadomości w aplikacjach Flutter

Wybierz platformę: iOS+ Android Web Flutter Unity C++


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 dowiedzieć się, jak zintegrować FCM z własną aplikacją, najpierw musisz poznać różne stany, w jakich może znajdować się urządzenie:

Stan Opis
Pierwszy plan gdy aplikacja jest otwarta, widoczna i używana;
Informacje ogólne gdy aplikacja jest otwarta, ale działa w tle (jest zminimalizowana); Zwykle dzieje się tak, gdy użytkownik naciśnie przycisk „Ekran główny” na urządzeniu, przełączy się na inną aplikację za pomocą przełącznika aplikacji lub otworzy aplikację na innej karcie (w internecie).
Zakończone gdy urządzenie jest zablokowane lub aplikacja nie jest uruchomiona;

Zanim aplikacja będzie mogła odbierać ładunki wiadomości za pomocą FCM, musi spełnić kilka warunków wstępnych:

  • Aplikacja musi zostać otwarta co najmniej raz (aby umożliwić rejestrację w FCM).
  • Jeśli na urządzeniu z iOS użytkownik usunie aplikację z przełącznika aplikacji, musi ją ponownie otworzyć ręcznie, aby wiadomości w tle znów zaczęły działać.
  • Jeśli na urządzeniu z Androidem użytkownik wymusi zamknięcie aplikacji w ustawieniach urządzenia, musi ją ponownie otworzyć ręcznie, aby wiadomości zaczęły działać.
  • W przypadku internetu musisz poprosić o token (za pomocą getToken()) z certyfikatem powiadomień push w internecie.

Prośba o uprawnienia do otrzymywania wiadomości

Na urządzeniach z iOS, macOS, Androidem 13 (lub nowszym) oraz w internecie, zanim będzie można odbierać ładunki FCM, musisz najpierw poprosić użytkownika o zgodę.

Pakiet firebase_messaging udostępnia interfejs API do wysyłania próśb o uprawnienia za pomocą metody requestPermission. Ten interfejs API akceptuje szereg argumentów nazwanych, które definiują typ uprawnień, o które chcesz poprosić, np. czy wiadomości zawierające ładunki powiadomień mogą wywoływać dźwięk lub odczytywać wiadomości za pomocą Siri. Domyślnie metoda żąda odpowiednich uprawnień domyślnych. Dokumentacja interfejsu API zawiera pełne informacje o tym, do czego służy każde uprawnienie.

Aby rozpocząć, wywołaj metodę z aplikacji (na iOS wyświetli się wbudowane okno modalne, a w internecie zostanie uruchomiony przepływ interfejsu 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}');

Właściwość authorizationStatus obiektu NotificationSettings zwróconego w odpowiedzi na żądanie może służyć do określenia ogólnej decyzji użytkownika:

  • authorized: użytkownik przyznał uprawnienie.
  • denied: użytkownik odmówił uprawnień.
  • notDetermined: użytkownik nie podjął jeszcze decyzji w sprawie udzielenia zgody,
  • provisional: użytkownik przyznał tymczasowe uprawnienia.

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

Gdy uprawnienia zostaną przyznane, a różne typy stanów urządzenia zostaną zrozumiane, aplikacja może 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 wiadomości wymagają różnych implementacji do ich obsługi:

Wiadomości na pierwszym planie

Aby obsługiwać wiadomości, gdy aplikacja działa na pierwszym planie, nasłuchuj strumienia 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 obiekt RemoteMessage, który zawiera różne informacje o ładunku, takie jak miejsce pochodzenia, unikalny identyfikator, czas wysłania, informację o tym, czy zawierał powiadomienie, i inne. Wiadomość została pobrana, gdy aplikacja działała na pierwszym planie, więc możesz bezpośrednio uzyskać dostęp do stanu i kontekstu aplikacji Flutter.

Wiadomości na pierwszym planie i wiadomości z powiadomieniem

Powiadomienia, które nadejdą podczas działania aplikacji na pierwszym planie, domyślnie nie będą wyświetlane na urządzeniach z Androidem ani iOS. Można jednak zastąpić to działanie:

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

Wiadomości w tle

Proces obsługi wiadomości w tle różni się na platformach Android, Apple i internetowych.

Platformy Apple i Android

Obsługuj wiadomości w tle, rejestrując moduł obsługi onBackgroundMessage. Po odebraniu wiadomości tworzony jest odizolowany proces (tylko na Androidzie, iOS/macOS nie wymaga osobnego odizolowanego procesu), który umożliwia obsługę wiadomości nawet wtedy, gdy aplikacja nie jest uruchomiona.

Jeśli chodzi o moduł obsługi wiadomości w tle, musisz pamiętać o kilku kwestiach:

  1. Nie może to być funkcja anonimowa.
  2. Musi to być funkcja najwyższego poziomu (np. nie metoda klasy, która wymaga inicjalizacji).
  3. Jeśli używasz Fluttera w wersji 3.3.0 lub nowszej, moduł obsługi wiadomości musi być oznaczony adnotacją @pragma('vm:entry-point') bezpośrednio nad deklaracją funkcji (w przeciwnym razie może zostać usunięty podczas usuwania nieużywanego kodu w trybie wersji).
@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 we własnym odizolowanym środowisku poza kontekstem aplikacji, nie można aktualizować stanu aplikacji ani wykonywać żadnych działań wpływających na interfejs. Możesz jednak wykonywać operacje logiczne, takie jak żądania HTTP, operacje wejścia/wyjścia (np. aktualizowanie pamięci lokalnej), komunikacja z innymi wtyczkami itp.

Zalecamy też jak najszybsze ukończenie logiki. Wykonywanie długich, intensywnych zadań wpływa na wydajność urządzenia i może spowodować, że system operacyjny zakończy proces. Jeśli zadania trwają dłużej niż 30 sekund, urządzenie może automatycznie zakończyć proces.

Sieć

W internecie napisz w języku JavaScript skrypt service worker, który działa w tle. Użyj skryptu service worker do obsługi wiadomości w tle.

Na początek utwórz nowy plik w katalogu web i nazwij go firebase-messaging-sw.js:

// See this file for the latest firebase-js-sdk version:
// https://github.com/firebase/flutterfire/blob/main/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);
});

Plik musi importować pakiety SDK aplikacji i wiadomości, inicjować Firebase i udostępniać zmienną messaging.

Następnie musisz zarejestrować pracownika. W pliku index.html zarejestruj skrypt service worker, modyfikując tag <script>, który uruchamia Fluttera:

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

Jeśli nadal używasz starego systemu szablonów, możesz zarejestrować skrypt service worker, modyfikując tag <script>, który uruchamia Fluttera, w ten sposób:

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

Następnie uruchom ponownie aplikację Flutter. Skrypt service worker zostanie zarejestrowany, a wszystkie wiadomości w tle będą obsługiwane za pomocą tego pliku.

Interakcja z obsługą

Powiadomienia są widoczne, więc użytkownicy często wchodzą z nimi w interakcję (naciskając je). Domyślnym działaniem zarówno na Androidzie, jak i na iOS jest otwarcie aplikacji. Jeśli aplikacja została zamknięta, zostanie uruchomiona. Jeśli działa w tle, zostanie przeniesiona na pierwszy plan.

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

Pakiet firebase-messaging umożliwia obsługę tej interakcji na 2 sposoby:

  • getInitialMessage(): jeśli aplikacja zostanie otwarta ze stanu zamknięcia, zostanie zwrócony obiekt Future zawierający RemoteMessage. Po wykorzystaniu RemoteMessage zostanie usunięty.
  • onMessageOpenedApp: Stream, który publikuje RemoteMessage, gdy aplikacja jest otwierana ze stanu działania w tle.

Zalecamy obsługę obu scenariuszy, aby zapewnić użytkownikom płynne działanie. Poniższy przykładowy kod pokazuje, jak to zrobić:

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 using 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. Poprzedni przykład pokazuje podstawową ilustrację z użyciem StatefulWidget.

Lokalizowanie wiadomości

Zlokalizowane ciągi znaków możesz wysyłać na 2 sposoby:

  • Zapisuj preferowany język każdego użytkownika na serwerze i wysyłaj spersonalizowane powiadomienia w każdym języku.
  • Umieść w aplikacji zlokalizowane ciągi tekstowe i wykorzystaj wbudowane ustawienia regionalne systemu operacyjnego.

Aby użyć drugiej metody:

Android

  1. Określ wiadomości w języku domyślnym w pliku 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 komunikaty w języku francuskim w pliku 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 kluczy title, message i body użyj kluczy 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 będzie wyglądał tak:

    {
      "android": {
         "notification": {
           "title_loc_key": "notification_title",
           "body_loc_key": "notification_message"
         }
      }
    }
    

iOS

  1. Określ wiadomości w języku domyślnym w pliku 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 komunikaty w języku francuskim w pliku fr.lproj/Localizable.strings:

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

    Ładunek wiadomości będzie wyglądał tak:

    {
      "apns": {
         "payload": {
           "alert": {
             "title-loc-key": "NOTIFICATION_TITLE",
             "loc-key": "NOTIFICATION_MESSAGE"
           }
         }
      }
    }
    

Włączanie eksportu danych o dostarczaniu wiadomości

Możesz wyeksportować dane wiadomości do BigQuery, aby przeprowadzić ich dalszą analizę. BigQuery umożliwia analizowanie danych za pomocą BigQuery SQL, eksportowanie ich do innego dostawcy chmury lub używanie ich w niestandardowych modelach ML. Eksport do BigQuery obejmuje wszystkie dostępne dane dotyczące wiadomości, niezależnie od ich typu i tego, czy zostały wysłane za pomocą interfejsu API czy Kreatora powiadomień.

Aby włączyć eksportowanie, najpierw wykonaj czynności opisane w dokumencie Eksportowanie danych do BigQuery. Włączenie tej funkcji automatycznie na poziomie instancji aplikacji umożliwia poproszenie użytkowników o zgodę na analizowanie danych o dostarczaniu wiadomości (zalecane). Aby automatycznie włączyć eksport, wykonaj te instrukcje:

Android

Możesz użyć tego kodu:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

iOS

W przypadku iOS musisz zmienić 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 aplikacji internetowych musisz zmienić skrypt service worker, aby używać wersji 9 pakietu SDK. Wersja 9 musi być spakowana, więc aby skrypt service worker działał, musisz użyć pakietu takiego jak esbuild. Aby dowiedzieć się, jak to zrobić, zapoznaj się z przykładową aplikacją.

Po przejściu na pakiet SDK w wersji 9 możesz użyć tego kodu:

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

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

Nie zapomnij uruchomić polecenia yarn build, aby wyeksportować nową wersję skryptu service worker do folderu web.

Wyświetlanie obrazów w powiadomieniach na iOS

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

Jeśli używasz uwierzytelniania Firebase przez telefon, musisz dodać do pliku Podfile pod Firebase Auth.

Krok 1. Dodaj rozszerzenie usługi powiadomień

  1. W Xcode kliknij File > New > Target… (Plik > Nowy > Cel…).
  2. Pojawi się okno z listą możliwych elementów docelowych. Przewiń lub użyj filtra, aby wybrać Notification Service Extension (Rozszerzenie usługi powiadomień). Kliknij Dalej.
  3. Dodaj nazwę produktu (użyj „ImageNotification”, aby postępować zgodnie z tym samouczkiem), wybierz Swift lub Objective-C i kliknij Zakończ.
  4. Włącz schemat, klikając Aktywuj.

Krok 2. Dodaj cel do pliku Podfile

Swift

Upewnij się, że nowe rozszerzenie ma dostęp do pakietu Swift FirebaseMessaging, dodając go do celu Runner:

  1. W Nawigatorze dodaj pakiet SDK Firebase na platformy Apple: File (Plik) > Add Package Dependencies... (Dodaj zależności pakietów...).

  2. Wyszukaj lub wpisz URL pakietu: none https://github.com/firebase/firebase-ios-sdk

  3. Dodaj do projektu Runner: Dodaj pakiet

  4. Wybierz FirebaseMessaging i dodaj do docelowego ImageNotification: Add Package (Dodaj pakiet).

Objective-C

Upewnij się, że nowe rozszerzenie ma dostęp do poda Firebase/Messaging, dodając go w pliku Podfile:

  1. W Nawigatorze otwórz plik Podfile: Pods > Podfile.

  2. Przejdź na dół 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 pody za pomocą polecenia pod install z katalogu ios lub macos.

Krok 3. Użyj asystenta rozszerzeń

Wszystko powinno działać normalnie. Ostatnim krokiem jest wywołanie pomocnika rozszerzenia.

Swift

  1. W nawigatorze wybierz rozszerzenie ImageNotification.

  2. Otwórz plik NotificationService.swift.

  3. Zastąp zawartość pliku NotificationService.swift tym kodem:

    import UserNotifications
    import FirebaseMessaging
    
    class NotificationService: UNNotificationServiceExtension {
    
        var contentHandler: ((UNNotificationContent) -> Void)?
        var bestAttemptContent: UNMutableNotificationContent?
    
        override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
            self.contentHandler = contentHandler
            bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
            Messaging.serviceExtension().populateNotificationContent(bestAttemptContent!, withContentHandler: contentHandler)
        }
    
        override func serviceExtensionTimeWillExpire() {
            if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
                contentHandler(bestAttemptContent)
            }
        }
    }
    

Objective-C

  1. W nawigatorze wybierz rozszerzenie ImageNotification.

  2. Otwórz plik NotificationService.m.

  3. Na górze pliku zaimportuj FirebaseMessaging.h zaraz po NotificationService.h.

    Zastąp zawartość pliku NotificationService.m tym kodem:

    #import "NotificationService.h"
    #import "FirebaseMessaging.h"
    #import <FirebaseAuth/FirebaseAuth-Swift.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 () <NSURLSessionDelegate>
    
    @property(nonatomic) void (^contentHandler)(UNNotificationContent *contentToDeliver);
    @property(nonatomic) 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

Do ładunku powiadomienia możesz teraz dodać obraz. Więcej informacji znajdziesz w artykule na temat tworzenia żądania wysłania.