Odbieranie wiadomości w aplikacjach Flutter

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


Wiadomości przychodzące są obsługiwane inaczej w zależności od stanu urządzenia. 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ńczona 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 użytkownik usunie aplikację z przełącznika aplikacji na iOS, 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

W systemach iOS, macOS, Android 13 (lub nowszym) oraz w internecie, zanim ładunki FCM będą mogły być odbierane na urządzeniu, 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 nazwanych argumentów, 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. W dokumentacji interfejsu API znajdziesz 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ócona w odpowiedzi na żądanie może służyć do określenia ogólnej decyzji użytkownika:

  • authorized: użytkownik przyznał uprawnienia.
  • 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ę o tym, czy określone uprawnienie jest włączone, wyłączone czy nieobsługiwane na bieżącym urządzeniu.

Po uzyskaniu uprawnień i zrozumieniu różnych typów stanu urządzenia 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, informacja 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

Wiadomości z powiadomieniami, które docierają, gdy aplikacja działa na pierwszym planie, domyślnie nie wyświetlają widocznego powiadomienia zarówno na Androidzie, jak i na 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), co umożliwia obsługę wiadomości nawet wtedy, gdy aplikacja nie jest uruchomiona.

Pamiętaj o kilku kwestiach dotyczących modułu 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 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 eliminacji martwego 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, 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 zostanie zarejestrowany, a wszystkie wiadomości w tle będą obsługiwane za pomocą tego pliku.

Obsługa interakcji

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 udostępnia 2 sposoby obsługi tej interakcji:

  • 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 pozwala poprosić 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ć, zobacz 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);

Pamiętaj, aby uruchomić 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ń do opcji Notification Service Extension (Rozszerzenie usługi powiadomień) lub użyj filtra, aby ją wybrać. 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: Plik > Dodaj zależności pakietu….

  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 początku 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.