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 przełączniku aplikacji na iOS, musi ją ponownie otworzyć, aby wiadomości w tle zaczęły znowu działać.
- Jeśli użytkownik na Androidzie wymusi zamknięcie aplikacji w ustawieniach urządzenia, 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 zezwolenie.
Pakiet firebase_messaging
udostępnia prosty interfejs API do wysyłania próśb o uprawnienia za pomocą metody requestPermission
.
Ten interfejs API akceptuje wiele 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 prosi o rozsądne uprawnienia domyślne. Interfejs API zawiera pełną dokumentację dotyczącą każdego uprawnienia.
Aby rozpocząć, wywołaj metodę z aplikacji (w iOS wyświetli się natywny moduł, a w internecie zostanie uruchomiony natywny interfejs 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
Wiadomości z powiadomieniami, które nadejdą, gdy aplikacja będzie 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 natywnych (Android i Apple) oraz 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żywanych funkcji 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 service workera 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
:
// Please 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 uruchom ponownie aplikację Flutter. Proces roboczy zostanie zarejestrowany, a wszystkie wiadomości w tle będą obsługiwane za pomocą 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ługiwać interakcję użytkownika po otwarciu aplikacji. Jeśli na przykład nowa wiadomość na czacie zostanie wysłana za pomocą powiadomienia i 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 obiektFuture
zawierający obiektRemoteMessage
. Po wykorzystaniuRemoteMessage
zostaną usunięte.onMessageOpenedApp
:Stream
, która wysyłaRemoteMessage
, 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 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 to podstawowa ilustracja 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 natywnych ustawień językowych 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 poluresources/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
,message
ibody
użyj kluczytitle_loc_key
ibody_loc_key
w przypadku zlokalizowanej wiadomości i ustaw je na atrybutname
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
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 polufr.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 przy użyciu BigQuery SQL, eksportowanie ich do innego dostawcy usług w chmurze lub wykorzystywanie ich w modelach ML. Eksport do BigQuery obejmuje wszystkie dostępne dane dotyczące wiadomości, niezależnie od ich typu i sposobu wysyłania (przez interfejs API czy kompozytor powiadomień).
Aby włączyć eksportowanie, najpierw wykonaj czynności opisane tutaj, a potem wykonaj te instrukcje:
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ć pakietu SDK w wersji 9.
Wersja 9 musi być spakowana, więc aby skrypt service worker działał, musisz użyć narzędzia do pakowania, np. esbuild
.
Zobacz przykładową aplikację, aby dowiedzieć się, jak to zrobić.
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ą w dół lub użyj filtra, aby wybrać Rozszerzenie usługi powiadomień. Kliknij Dalej.
- Dodaj nazwę produktu (w tym samouczku użyj nazwy „ImageNotification”), wybierz
Swift
lubObjective-C
i 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 FirebaseMessaging
pakietu SwiftRunner
, dodając go do Runner
celuRunner
:
W Nawigatorze dodaj pakiet SDK Firebase na platformy Apple: Plik > Dodaj zależności pakietu.
Wyszukaj lub wpisz adres URL pakietu:
https://github.com/firebase/firebase-ios-sdk
Dodaj do projektu
Runner
: Dodaj pakietWybierz 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.
Przewiń plik do końca 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 install
z kataloguios
lubmacos
.
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.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
W nawigatorze wybierz rozszerzenie ImageNotification.
Otwórz plik
NotificationService.m
.U góry pliku zaimportuj
FirebaseMessaging.h
bezpośrednio poNotificationService.h
, jak pokazano poniżej.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
W ładunku powiadomienia możesz teraz dodać obraz. Zapoznaj się z dokumentacją iOS na temat tworzenia prośby o wysłanie. Pamiętaj, że urządzenie wymusza maksymalny rozmiar obrazu wynoszący 300 KB.