Z tego przewodnika dowiesz się, jak skonfigurować Firebase Cloud Messaging w aplikacjach klienckich na urządzenia mobilne i w internecie, aby niezawodnie odbierać wiadomości.
Odbieranie wiadomości w aplikacji Flutter
Wiadomości przychodzące są obsługiwane w różny sposób 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 ekranu głównego na urządzeniu, przełączy się na inną aplikację za pomocą przełącznika aplikacji lub otworzy aplikację w innej karcie (w internecie). | 
| Zakończono | Gdy urządzenie jest zablokowane lub aplikacja nie jest uruchomiona. | 
Zanim aplikacja będzie mogła otrzymywać ł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 przesunie aplikację w górę w przełączniku aplikacji na iOS, musi ją ponownie otworzyć, aby wiadomości w tle zaczęły znowu działać.
- Jeśli użytkownik wymusi zamknięcie aplikacji na urządzeniu z Androidem, musi ją ponownie otworzyć, 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 odbierania wiadomości
Na urządzeniach z iOS, macOS, Androidem 13 (lub nowszym) i w internecie, zanim na urządzeniu 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 kilka nazwanych argumentów, które określają 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 ta metoda wysyła prośbę o przyznanie rozsądnych uprawnień domyślnych. Interfejs API zawiera pełną dokumentację dotyczącą każdego uprawnienia.
Aby rozpocząć, wywołaj metodę z aplikacji (na iOS wyświetli się wbudowane okno modalne, a w internecie zostanie wywołany 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 przez żądanie może służyć do określania ogólnej decyzji użytkownika:
- authorized: użytkownik przyznał uprawnienia.
- denied: użytkownik odmówił przyznania uprawnień.
- notDetermined: użytkownik nie zdecydował jeszcze, czy chce udzielić zgody.
- provisional: użytkownik przyznał tymczasowe uprawnienia.
Pozostałe właściwości w NotificationSettings zwracają informację, czy określone uprawnienie jest włączone, wyłączone lub nie jest obsługiwane na bieżącym urządzeniu.
Gdy uprawnienia zostaną przyznane i zrozumiesz różne typy stanu urządzenia, aplikacja będzie mogła 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:
Wiadomości na pierwszym planie
Aby obsługiwać wiadomości, gdy aplikacja jest 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 RemoteMessage, w którym znajdują się szczegółowe informacje o ładunku, takie jak źródło, niepowtarzalny identyfikator, czas wysłania, czy zawierał on powiadomienie itp. Ponieważ wiadomość została pobrana, gdy aplikacja działał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 nadejdą, gdy aplikacja działa na pierwszym planie, nie będą domyślnie wyświetlać widocznego powiadomienia na Androidzie ani iOS. Możesz jednak zastąpić to działanie:
- Na Androidzie musisz utworzyć kanał powiadomień o wysokim priorytecie.
- Na urządzeniach z 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 otrzymaniu wiadomości tworzony jest odseparowany proces (tylko na Androidzie, iOS/macOS nie wymagają osobnego odseparowanego procesu), który umożliwia obsługę wiadomości nawet wtedy, gdy aplikacja nie jest uruchomiona.
Oto kilka kwestii, o których warto pamiętać w przypadku funkcji obsługi wiadomości w tle:
- Nie może to być funkcja anonimowa.
- Musi to być funkcja najwyższego poziomu (np. nie metoda klasy, która wymaga inicjowania).
- Jeśli używasz Fluttera w wersji 3.3.0 lub nowszej, obsługę wiadomości musisz oznaczyć adnotacją @pragma('vm:entry-point')bezpośrednio nad deklaracją funkcji (w przeciwnym razie może ona zostać usunięta podczas usuwania nieużywanego kodu w trybie wydania).
@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 w izolowanym środowisku poza kontekstem aplikacji, nie można aktualizować stanu aplikacji ani wykonywać żadnej logiki wpływającej na interfejs. Możesz jednak wykonywać logikę, taką jak żądania HTTP, operacje wejścia/wyjścia (np. aktualizowanie pamięci lokalnej), komunikować się z innymi wtyczkami itp.
Zalecamy też jak najszybsze ukończenie logiki. Wykonywanie długich, wymagających zadań wpływa na wydajność urządzenia i może spowodować zakończenie procesu przez system operacyjny. Jeśli zadania działają dłużej niż 30 sekund, urządzenie może automatycznie zakończyć proces.
Sieć
W internecie napisz skrypt Service Worker w JavaScript, który działa w tle. Użyj procesu roboczego usługi do obsługi wiadomości w tle.
Na początek utwórz nowy plik w katalogu web i nadaj mu nazwę 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 usługi przesyłania wiadomości, inicjować Firebase i udostępniać zmienną messaging.
Następnie należy zarejestrować pracownika. W pliku index.html zarejestruj instancję roboczą, 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ć proces roboczy, 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 ponownie uruchom aplikację Flutter. Instancja robocza zostanie zarejestrowana, a wszystkie wiadomości w tle będą obsługiwane przy użyciu tego pliku.
Obsługa interakcji
Powiadomienia są widocznym sygnałem, dlatego użytkownicy często wchodzą z nimi w interakcję (naciskając je). Domyślnym działaniem zarówno na Androidzie, jak i iOS jest otwarcie aplikacji. Jeśli aplikacja jest 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, po otwarciu aplikacji możesz chcieć otworzyć konkretną rozmowę.
Pakiet firebase-messaging udostępnia 2 sposoby obsługi tej interakcji:
- getInitialMessage(): jeśli aplikacja zostanie otwarta po zamknięciu, zwrócony zostanie obiekt- Futurezawierający obiekt- RemoteMessage. Po wykorzystaniu- RemoteMessagezostaną usunięte.
- onMessageOpenedApp:- Stream, która wysyła- RemoteMessage, gdy aplikacja jest otwierana z tła.
Zalecamy obsługę obu scenariuszy, aby zapewnić użytkownikom płynne działanie aplikacji. 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 widżetu StatefulWidget.
Lokalizowanie wiadomości
Zlokalizowane ciągi znaków możesz wysyłać na 2 sposoby:
- Zapisuj na serwerze preferowany język każdego użytkownika i wysyłaj dostosowane powiadomienia w poszczególnych językach.
- Osadzaj w aplikacji zlokalizowane ciągi znaków i korzystaj z wbudowanych ustawień regionalnych systemu operacyjnego.
Oto jak używać drugiej metody:
Android
- 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>
- Określ przetłumaczone wiadomości w katalogu - values-language. Na przykład określ wiadomości w języku francuskim w polu- resources/values-fr/strings.xml:- <string name="notification_title">Bonjour le monde</string> <string name="notification_message">C'est un message</string>
- W ładunku serwera zamiast kluczy - title,- messagei- bodyużyj kluczy- title_loc_keyi- body_loc_keyw przypadku zlokalizowanej wiadomości i ustaw je na atrybut- namewiadomoś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
- Określ wiadomości w języku domyślnym w - Base.lproj/Localizable.strings:- "NOTIFICATION_TITLE" = "Hello World"; "NOTIFICATION_MESSAGE" = "This is a message";
- Określ przetłumaczone wiadomości w katalogu - language.lproj. Na przykład określ wiadomości w języku francuskim w polu- 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 eksportowania danych o dostarczaniu wiadomości
Dane wiadomości możesz eksportować do BigQuery w celu dalszej analizy. BigQuery umożliwia analizowanie danych za pomocą BigQuery SQL, eksportowanie ich do innego dostawcy usług w chmurze lub używanie ich w modelach ML. Eksport do BigQuery obejmuje wszystkie dostępne dane o wiadomościach, niezależnie od ich typu i sposobu wysyłania (za pomocą interfejsu API lub kompozytora powiadomień).
Aby włączyć eksportowanie, wykonaj najpierw czynności opisane w dokumencie Eksportowanie danych do BigQuery. Włączenie tej funkcji na poziomie instancji aplikacji umożliwia proszenie użytkowników o zgodę na analizowanie danych o dostarczaniu wiadomości (zalecane). Aby włączyć eksportowanie za pomocą kodu:
Android
Możesz użyć tego kodu:
await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);
iOS
W przypadku iOS musisz zmienić AppDelegate.m na poniższą zawartość.
#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 internetu musisz zmienić proces roboczy usługi, aby używać wersji 9 pakietu SDK.
Wersja 9 musi być spakowana, więc aby skrypt service worker działał, musisz użyć narzędzia do pakowania, takiego jak esbuild.
Aby dowiedzieć się, jak to zrobić, zapoznaj się z przykładową aplikacją.
Po migracji do pakietu 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ę pliku service worker do folderu web.
Wyświetlanie obrazów w powiadomieniach na urządzeniach z 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 używasz uwierzytelniania telefonicznego Firebase, musisz dodać do pliku Podfile pakiet Firebase Auth.
Krok 1. Dodaj rozszerzenie usługi powiadomień
- W Xcode kliknij File (Plik) > New (Nowy) > Target (Cel)…
- W oknie modalnym pojawi się lista możliwych elementów docelowych. Przewiń ją lub użyj filtra, aby wybrać Rozszerzenie usługi powiadomień. Kliknij Dalej.
- Dodaj nazwę produktu (w tym samouczku użyj nazwy „ImageNotification”), wybierz SwiftlubObjective-Ci kliknij Zakończ.
- Aby włączyć schemat, kliknij Aktywuj.
Krok 2. Dodaj cel do pliku Podfile
Swift
Sprawdź, czy nowe rozszerzenie ma dostęp do FirebaseMessagingpakietu SwiftRunner, dodając go do Runnerelementu docelowego:
- W Nawigatorze dodaj pakiet SDK Firebase na platformy Apple: Plik > Dodaj zależności pakietu. 
- Wyszukaj lub wpisz adres URL pakietu: - none https://github.com/firebase/firebase-ios-sdk
- Dodaj do projektu - Runner: Dodaj pakiet
- Wybierz FirebaseMessaging i dodaj do celu ImageNotification: Add Package (Dodaj pakiet). 
Objective-C
Upewnij się, że nowe rozszerzenie ma dostęp do komponentu Firebase/Messaging, dodając go do pliku Podfile:
- W Nawigatorze otwórz plik Podfile: Pods > Podfile. 
- 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
- Zainstaluj lub zaktualizuj pody za pomocą - pod installz katalogu- ioslub- macos.
Krok 3. Użyj narzędzia pomocniczego do rozszerzeń
W tym momencie wszystko powinno działać normalnie. Ostatnim krokiem jest wywołanie narzędzia pomocniczego rozszerzenia.
Swift
- W nawigatorze wybierz rozszerzenie ImageNotification. 
- Otwórz plik - NotificationService.swift.
- Zastąp zawartość pliku - NotificationService.swifttym 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
- W nawigatorze wybierz rozszerzenie ImageNotification. 
- Otwórz plik - NotificationService.m.
- Na początku pliku za znakiem - NotificationService.hwstaw znak- FirebaseMessaging.h.- Zastąp zawartość pliku - NotificationService.mtym 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 o tworzeniu żądania wysyłania.