A seconda dello stato di un dispositivo, i messaggi in arrivo vengono gestiti in modo diverso. Per comprendere questi scenari e come integrare FCM nella tua applicazione, è importante innanzitutto stabilire i vari stati in cui può trovarsi un dispositivo:
Stato | Descrizione |
---|---|
In primo piano | Quando l'applicazione è aperta, in visualizzazione e in uso. |
Sfondo | Quando l'applicazione è aperta, ma in background (ridotta a icona). In genere, questo si verifica quando l'utente ha premuto il pulsante "Home" sul dispositivo, è passato a un'altra app utilizzando il selettore di app o ha aperto l'applicazione in un'altra scheda (web). |
Risolto | Quando il dispositivo è bloccato o l'applicazione non è in esecuzione. |
Prima che l'applicazione possa ricevere payload di messaggi tramite FCM, devono essere soddisfatte alcune precondizioni:
- L'applicazione deve essere stata aperta almeno una volta (per consentire la registrazione con FCM).
- Su iOS, se l'utente chiude l'applicazione dal selettore app, deve riaprirla manualmente affinché i messaggi in background inizino a funzionare di nuovo.
- Su Android, se l'utente chiude forzatamente l'app dalle impostazioni del dispositivo, deve riaprirla manualmente per far funzionare i messaggi.
- Sul web, devi aver richiesto un token (utilizzando
getToken()
) con il certificato push web.
Richiedere l'autorizzazione a ricevere messaggi
Su iOS, macOS, web e Android 13 (o versioni successive), prima che i payload FCM possano essere ricevuti sul tuo dispositivo, devi prima chiedere l'autorizzazione all'utente.
Il pacchetto firebase_messaging
fornisce una semplice API per richiedere l'autorizzazione tramite il metodo requestPermission
.
Questa API accetta una serie di argomenti denominati che definiscono il tipo di autorizzazioni che vuoi richiedere, ad esempio se
i messaggi contenenti payload di notifica possono attivare un suono o leggere i messaggi tramite Siri. Per impostazione predefinita,
il metodo richiede autorizzazioni predefinite sensibili. L'API di riferimento fornisce una documentazione completa sullo scopo di ogni autorizzazione.
Per iniziare, chiama il metodo dalla tua applicazione (su iOS verrà visualizzata una finestra modale nativa, sul web verrà attivato il flusso dell'API nativa del browser):
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}');
La proprietà authorizationStatus
dell'oggetto NotificationSettings
restituito dalla richiesta può essere utilizzata per determinare la decisione complessiva dell'utente:
authorized
: l'utente ha concesso l'autorizzazione.denied
: l'utente ha negato l'autorizzazione.notDetermined
: l'utente non ha ancora scelto se concedere l'autorizzazione.provisional
: L'utente ha concesso l'autorizzazione provvisoria
Le altre proprietà di NotificationSettings
indicano se una specifica autorizzazione è attivata, disattivata o non supportata sul dispositivo
corrente.
Una volta concessa l'autorizzazione e compresi i diversi tipi di stato del dispositivo, l'applicazione può iniziare a gestire i payload FCM in entrata.
Gestione dei messaggi
In base allo stato attuale dell'applicazione, i payload in entrata di diversi tipi di messaggi richiedono implementazioni diverse per gestirli:
Messaggi in primo piano
Per gestire i messaggi mentre l'applicazione è in primo piano, ascolta lo stream 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}');
}
});
Lo stream contiene un RemoteMessage
, che descrive
varie informazioni sul payload, ad esempio la sua origine, l'ID univoco, l'ora di invio, se conteneva
una notifica e altro ancora. Poiché il messaggio è stato recuperato mentre l'applicazione è in primo piano, puoi accedere direttamente allo stato e al contesto dell'applicazione Flutter.
Messaggi in primo piano e di notifica
I messaggi di notifica che arrivano mentre l'applicazione è in primo piano non mostrano una notifica visibile per impostazione predefinita, sia su Android che su iOS. Tuttavia, è possibile eseguire l'override di questo comportamento:
- Su Android, devi creare un canale di notifica "Priorità elevata".
- Su iOS, puoi aggiornare le opzioni di presentazione dell'applicazione.
Messaggi in background
La procedura di gestione dei messaggi in background è diversa sulle piattaforme native (Android e Apple) e su quelle basate sul web.
Piattaforme Apple e Android
Gestisci i messaggi in background registrando un gestore onBackgroundMessage
. Quando vengono ricevuti i messaggi, viene creato un
isolato (solo Android, iOS/macOS non richiede un isolato separato) che ti consente di gestire i messaggi anche quando l'applicazione non è in esecuzione.
Ecco alcuni aspetti da tenere presente in merito al gestore dei messaggi in background:
- Non deve essere una funzione anonima.
- Deve essere una funzione di primo livello (ad es. non un metodo di classe che richiede l'inizializzazione).
- Quando utilizzi Flutter versione 3.3.0 o successive, il gestore dei messaggi deve essere annotato con
@pragma('vm:entry-point')
subito sopra la dichiarazione della funzione (altrimenti potrebbe essere rimosso durante l'eliminazione del codice non utilizzato per la modalità di rilascio).
@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());
}
Poiché il gestore viene eseguito nel proprio isolamento al di fuori del contesto delle applicazioni, non è possibile aggiornare lo stato dell'applicazione o eseguire qualsiasi logica che influenzi la UI. Tuttavia, puoi eseguire operazioni logiche come richieste HTTP, operazioni I/O (ad es. aggiornamento dell'archiviazione locale), comunicare con altri plug-in e così via.
Ti consigliamo inoltre di completare la logica il prima possibile. L'esecuzione di attività lunghe e intensive influisce sulle prestazioni del dispositivo e potrebbe causare l'interruzione del processo da parte del sistema operativo. Se le attività vengono eseguite per più di 30 secondi, il dispositivo potrebbe interrompere automaticamente il processo.
Web
Sul web, scrivi un service worker JavaScript che viene eseguito in background. Utilizza il service worker per gestire i messaggi in background.
Per iniziare, crea un nuovo file nella directory web
e chiamalo 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);
});
Il file deve importare gli SDK dell'app e di messaggistica, inizializzare Firebase ed esporre la variabile messaging
.
Successivamente, il lavoratore deve essere registrato. All'interno del file index.html
, registra il worker modificando il tag <script>
che avvia Flutter:
<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>
Se utilizzi ancora il vecchio sistema di modelli, puoi registrare il service worker modificando il tag <script>
che esegue il bootstrap di Flutter nel seguente modo:
<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>
Dopodiché riavvia l'applicazione Flutter. Il lavoratore verrà registrato e tutti i messaggi in background verranno gestiti tramite questo file.
Interazione con la gestione
Poiché le notifiche sono un segnale visibile, è comune che gli utenti interagiscano con loro (premendo). Il comportamento predefinito sia su Android che su iOS è l'apertura dell'applicazione. Se l'applicazione viene terminata, verrà avviata; se è in background, verrà portata in primo piano.
A seconda del contenuto di una notifica, potresti voler gestire l'interazione dell'utente all'apertura dell'applicazione. Ad esempio, se viene inviato un nuovo messaggio di chat tramite una notifica e l'utente la preme, potresti voler aprire la conversazione specifica quando l'applicazione si apre.
Il pacchetto firebase-messaging
offre due modi per gestire questa interazione:
getInitialMessage()
: se l'applicazione viene aperta da uno stato di terminazione, verrà restituito unFuture
contenente unRemoteMessage
. Una volta consumato,RemoteMessage
verrà rimosso.onMessageOpenedApp
: unStream
che pubblica unRemoteMessage
quando l'applicazione viene aperta da uno stato in background.
È consigliabile gestire entrambi gli scenari per garantire un'esperienza utente ottimale. L'esempio di codice riportato di seguito illustra come è possibile ottenere questo risultato:
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("...");
}
}
La gestione dell'interazione dipende dalla configurazione dell'applicazione. L'esempio precedente mostra un'illustrazione di base che utilizza un widget StatefulWidget.
Localizzare i messaggi
Puoi inviare stringhe localizzate in due modi diversi:
- Memorizza la lingua preferita di ciascun utente nel tuo server e invia notifiche personalizzate per ogni lingua
- Incorpora stringhe localizzate nella tua app e utilizza le impostazioni internazionali native del sistema operativo
Ecco come utilizzare il secondo metodo:
Android
Specifica i messaggi nella lingua predefinita in
resources/values/strings.xml
:<string name="notification_title">Hello world</string> <string name="notification_message">This is a message</string>
Specifica i messaggi tradotti nella directory
values-language
. Ad esempio, specifica i messaggi in francese inresources/values-fr/strings.xml
:<string name="notification_title">Bonjour le monde</string> <string name="notification_message">C'est un message</string>
Nel payload del server, anziché utilizzare le chiavi
title
,message
ebody
, utilizzatitle_loc_key
ebody_loc_key
per il messaggio localizzato e impostale sull'attributoname
del messaggio che vuoi visualizzare.Il payload del messaggio sarà simile al seguente:
{ "android": { "notification": { "title_loc_key": "notification_title", "body_loc_key": "notification_message" } } }
iOS
Specifica i messaggi nella lingua predefinita in
Base.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Hello World"; "NOTIFICATION_MESSAGE" = "This is a message";
Specifica i messaggi tradotti nella directory
language.lproj
. Ad esempio, specifica i messaggi in francese infr.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Bonjour le monde"; "NOTIFICATION_MESSAGE" = "C'est un message";
Il payload del messaggio sarà simile al seguente:
{ "apns": { "payload": { "alert": { "title-loc-key": "NOTIFICATION_TITLE", "loc-key": "NOTIFICATION_MESSAGE" } } } }
Abilitare l'esportazione dei dati di pubblicazione dei messaggi
Puoi esportare i dati dei messaggi in BigQuery per ulteriori analisi. BigQuery ti consente di analizzare i dati utilizzando BigQuery SQL, esportarli in un altro provider di servizi cloud o utilizzarli per i tuoi modelli ML personalizzati. Un'esportazione in BigQuery include tutti i dati disponibili per i messaggi, indipendentemente dal tipo di messaggio o dal fatto che il messaggio venga inviato tramite l'API o il compositore di notifiche.
Per attivare l'esportazione, segui prima i passaggi descritti qui, poi segui queste istruzioni:
Android
Puoi utilizzare il seguente codice:
await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);
iOS
Per iOS, devi modificare AppDelegate.m
con i seguenti contenuti.
#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
Web
Per il web, devi modificare il service worker per utilizzare la versione 9 dell'SDK.
La versione 9 deve essere raggruppata, quindi devi utilizzare un bundler come esbuild
, ad esempio,
per far funzionare il service worker.
Per scoprire come fare, consulta l'app di esempio.
Una volta eseguita la migrazione all'SDK v9, puoi utilizzare il seguente codice:
import {
experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
getMessaging,
} from 'firebase/messaging/sw';
...
const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);
Non dimenticare di eseguire yarn build
per esportare la nuova versione del service worker nella cartella web
.
Visualizzare le immagini nelle notifiche su iOS
Sui dispositivi Apple, affinché le notifiche FCM in arrivo mostrino le immagini del payload FCM, devi aggiungere un'estensione del servizio di notifica aggiuntiva e configurare la tua app per utilizzarla.
Se utilizzi l'autenticazione tramite numero di telefono Firebase, devi aggiungere il pod Firebase Auth al tuo Podfile.
Passaggio 1: aggiungi un'estensione del servizio di notifica
- In Xcode, fai clic su File > New > Target… (File > Nuovo > Target…).
- Una finestra modale mostrerà un elenco di possibili target; scorri verso il basso o utilizza il filtro per selezionare Estensione del servizio di notifica. Fai clic su Avanti.
- Aggiungi un nome prodotto (utilizza "ImageNotification" per seguire questo tutorial), seleziona
Swift
oObjective-C
e fai clic su Fine. - Attiva lo schema facendo clic su Attiva.
Passaggio 2: aggiungi la destinazione al Podfile
Swift
Assicurati che la nuova estensione abbia accesso al pacchetto FirebaseMessaging
Swift aggiungendolo al target Runner
:
Dal navigatore, aggiungi l'SDK delle piattaforme Apple di Firebase: File > Add Package Dependencies… (File > Aggiungi dipendenze pacchetto…)
Cerca o inserisci l'URL del pacchetto:
https://github.com/firebase/firebase-ios-sdk
Aggiungi al progetto
Runner
: Aggiungi pacchettoScegli FirebaseMessaging e aggiungilo all'immagine di notifica di destinazione: Aggiungi pacchetto
Objective-C
Assicurati che la nuova estensione abbia accesso al pod Firebase/Messaging
aggiungendolo al Podfile:
Dal Navigator, apri il Podfile: Pods > Podfile
Scorri fino in fondo al file e aggiungi:
target 'ImageNotification' do use_frameworks! pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication pod 'Firebase/Messaging' end
Installa o aggiorna i pod utilizzando
pod install
dalla directoryios
omacos
.
Passaggio 3: utilizza l'helper per le estensioni
A questo punto, tutto dovrebbe funzionare normalmente. Il passaggio finale consiste nell'invocare l'helper dell'estensione.
Swift
Nel navigatore, seleziona l'estensione ImageNotification.
Apri il file
NotificationService.swift
.Sostituisci i contenuti di
NotificationService.swift
con: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
Nel navigatore, seleziona l'estensione ImageNotification.
Apri il file
NotificationService.m
.All'inizio del file, importa
FirebaseMessaging.h
subito dopoNotificationService.h
, come mostrato di seguito.Sostituisci i contenuti di
NotificationService.m
con:#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
Passaggio 4: aggiungi l'immagine al payload
Ora puoi aggiungere un'immagine al payload della notifica. Consulta la documentazione di iOS su come creare una richiesta di invio. Tieni presente che la dimensione massima dell'immagine di 300 KB è imposta dal dispositivo.