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 metoda żąda rozsądnych uprawnień domyślnych. 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 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 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.