了解 2023 年 Google I/O 大会上介绍的 Firebase 亮点。了解详情

Ricevi messaggi in un'app Flutter

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 propria applicazione, è innanzitutto importante stabilire i vari stati in cui può trovarsi un dispositivo:

Stato Descrizione
Primo piano Quando l'applicazione è aperta, in vista e in uso.
Sfondo Quando l'applicazione è aperta, ma in background (ridotta a icona). Ciò si verifica in genere quando l'utente ha premuto il pulsante "home" sul dispositivo, è passato a un'altra app utilizzando il selettore di app o ha l'applicazione aperta in una scheda diversa (web).
Terminato Quando il dispositivo è bloccato o l'applicazione non è in esecuzione.

Ci sono alcune condizioni preliminari che devono essere soddisfatte prima che l'applicazione possa ricevere i payload dei messaggi tramite FCM:

  • L'applicazione deve essere stata aperta almeno una volta (per consentire la registrazione con FCM).
  • Su iOS, se l'utente rimuove l'applicazione dal selettore di app, deve essere riaperta manualmente affinché i messaggi in background ricomincino a funzionare.
  • Su Android, se l'utente chiude forzatamente l'app dalle impostazioni del dispositivo, deve essere riaperta manualmente affinché i messaggi inizino a funzionare.
  • Sul web, devi aver richiesto un token (usando getToken() ) con il tuo certificato web push.

Richiedi il permesso di ricevere messaggi (Apple e Web)

Su iOS, macOS e web, prima che i payload FCM possano essere ricevuti sul tuo dispositivo, devi prima chiedere il permesso dell'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 desideri 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 la documentazione completa sullo scopo di ciascuna autorizzazione.

Per iniziare, chiama il metodo dalla tua applicazione (su iOS verrà visualizzato un modale nativo, sul Web verrà attivato il flusso API nativo 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à su NotificationSettings restituiscono se un'autorizzazione specifica è abilitata, disabilitata o non supportata sul dispositivo corrente.

Una volta che l'autorizzazione è stata concessa e i diversi tipi di stato del dispositivo sono stati compresi, la tua applicazione può ora iniziare a gestire i payload FCM in arrivo.

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 il flusso 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}');
  }
});

Il flusso contiene un RemoteMessage , che descrive in dettaglio varie informazioni sul payload, ad esempio da dove proveniva, l'ID univoco, l'ora di invio, se conteneva una notifica e altro. Poiché il messaggio è stato recuperato mentre la tua applicazione è in primo piano, puoi accedere direttamente allo stato e al contesto della tua applicazione Flutter.

Messaggi in primo piano e di notifica

I messaggi di notifica che arrivano mentre l'applicazione è in primo piano non visualizzeranno una notifica visibile per impostazione predefinita, sia su Android che su iOS. Tuttavia, è possibile ignorare questo comportamento:

  • Su Android, devi creare un canale di notifica "Alta priorità".
  • Su iOS, puoi aggiornare le opzioni di presentazione per l'applicazione.

Messaggi di sottofondo

Il processo di gestione dei messaggi in background è diverso sulle piattaforme native (Android e Apple) e basate sul Web.

Piattaforme Apple e Android

Gestisci i messaggi in background registrando un gestore onBackgroundMessage . Quando i messaggi vengono ricevuti, viene generato un isolato (solo Android, iOS/macOS non richiede un isolato separato) che consente di gestire i messaggi anche quando l'applicazione non è in esecuzione.

Ci sono alcune cose da tenere a mente sul gestore dei messaggi in background:

  1. Non deve essere una funzione anonima.
  2. Deve essere una funzione di primo livello (ad esempio non un metodo di classe che richiede l'inizializzazione).
  3. Quando si utilizza Flutter versione 3.3.0 o successiva, il gestore del messaggio deve essere annotato con @pragma('vm:entry-point') proprio sopra la dichiarazione della funzione (altrimenti potrebbe essere rimosso durante lo scuotimento dell'albero 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 alcuna logica che influisca sull'interfaccia utente. Puoi, tuttavia, eseguire logiche come richieste HTTP, eseguire operazioni IO (ad es. Aggiornamento della memoria locale), comunicare con altri plug-in ecc.

Si consiglia inoltre di completare la logica il prima possibile. L'esecuzione di attività lunghe e intense influisce sulle prestazioni del dispositivo e può causare l'interruzione del processo da parte del sistema operativo. Se le attività vengono eseguite per più di 30 secondi, il dispositivo potrebbe terminare automaticamente il processo.

ragnatela

Sul Web, scrivi un Service Worker JavaScript che viene eseguito in background. Utilizzare il service worker per gestire i messaggi in background.

Per iniziare, crea un nuovo file nella tua directory web e chiamalo firebase-messaging-sw.js :

importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.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 sia l'app sia gli SDK di messaggistica, inizializzare Firebase ed esporre la variabile messaging .

Successivamente, il lavoratore deve essere registrato. All'interno del file di immissione, dopo che il file main.dart.js è stato caricato, registra il tuo lavoratore:

<html>
<body>
  ...
  <script src="main.dart.js" type="application/javascript"></script>
  <script>
       if ('serviceWorker' in navigator) {
          // Service workers are supported. Use them.
          window.addEventListener('load', function () {
            // ADD THIS LINE
            navigator.serviceWorker.register('/firebase-messaging-sw.js');

            // 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;

            //  ...
          });
      }
  </script>

Quindi riavvia l'applicazione Flutter. Il lavoratore verrà registrato e tutti i messaggi in background verranno gestiti tramite questo file.

Gestire l'interazione

Poiché le notifiche sono un segnale visibile, è normale che gli utenti interagiscano con esse (premendo). Il comportamento predefinito su Android e iOS è aprire l'applicazione. Se l'applicazione viene terminata, verrà avviata; se è sullo sfondo verrà portato in primo piano.

A seconda del contenuto di una notifica, potresti voler gestire l'interazione dell'utente all'apertura dell'applicazione. Ad esempio, se un nuovo messaggio di chat viene inviato tramite una notifica e l'utente lo preme, potresti voler aprire la conversazione specifica quando si apre l'applicazione.

Il pacchetto firebase-messaging fornisce due modi per gestire questa interazione:

  • getInitialMessage() : se l'applicazione viene aperta da uno stato terminato, verrà restituito un Future contenente un RemoteMessage . Una volta consumato, il RemoteMessage verrà rimosso.
  • onMessageOpenedApp : un Stream che pubblica un RemoteMessage quando l'applicazione viene aperta da uno stato in background.

Si consiglia di gestire entrambi gli scenari per garantire un'esperienza utente fluida per gli utenti. L'esempio di codice seguente illustra come 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("...");
  }
}

Il modo in cui gestisci l'interazione dipende dalla configurazione dell'applicazione. L'esempio precedente mostra un'illustrazione di base utilizzando uno StatefulWidget.

Localizzare i messaggi

Puoi inviare stringhe localizzate in due modi diversi:

  • Memorizza la lingua preferita di ciascuno dei tuoi utenti nel tuo server e invia notifiche personalizzate per ogni lingua
  • Incorpora stringhe localizzate nella tua app e utilizza le impostazioni locali native del sistema operativo

Ecco come utilizzare il secondo metodo:

Androide

  1. Specifica i tuoi 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>
    
  2. Specificare i messaggi tradotti nella directory values- language . Ad esempio, specifica i messaggi in francese in resources/values-fr/strings.xml :

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. Nel payload del server, invece di utilizzare le chiavi title , message e body , utilizza title_loc_key e body_loc_key per il messaggio localizzato e impostale sull'attributo name del messaggio che desideri visualizzare.

    Il payload del messaggio sarebbe simile a questo:

    {
      "data": {
        "title_loc_key": "notification_title",
        "body_loc_key": "notification_message"
      }
    }
    

iOS

  1. Specifica i tuoi messaggi nella lingua predefinita in Base.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Specificare i messaggi tradotti nella directory language .lproj . Ad esempio, specifica i messaggi in francese in fr.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Bonjour le monde";
    "NOTIFICATION_MESSAGE" = "C'est un message";
    

    Il payload del messaggio sarebbe simile a questo:

    {
      "data": {
        "title_loc_key": "NOTIFICATION_TITLE",
        "body_loc_key": "NOTIFICATION_MESSAGE"
      }
    }
    

Abilita l'esportazione dei dati di recapito dei messaggi

Puoi esportare i dati dei tuoi messaggi in BigQuery per ulteriori analisi. BigQuery ti consente di analizzare i dati utilizzando BigQuery SQL, esportarli in un altro provider cloud o utilizzare i dati 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 abilitare l'esportazione, segui prima i passaggi descritti qui , quindi segui queste istruzioni:

Androide

Puoi usare il seguente codice:

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

iOS

Per iOS, è necessario modificare AppDelegate.m con il seguente contenuto.

#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

ragnatela

Per il Web, è necessario modificare il service worker per poter utilizzare la versione v9 dell'SDK. La versione v9 deve essere raggruppata, quindi è necessario utilizzare un bundler come esbuild ad esempio, per far funzionare il service worker. Guarda l'app di esempio per vedere come raggiungere questo obiettivo.

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 tuo service worker nella cartella web .

Visualizza le immagini nelle notifiche su iOS

Sui dispositivi Apple, affinché le notifiche FCM in arrivo visualizzino le immagini dal payload FCM, devi aggiungere un'ulteriore estensione del servizio di notifica e configurare la tua app per utilizzarla.

Se utilizzi l'autenticazione telefonica Firebase, devi aggiungere il pod Firebase Auth al tuo Podfile.

Passaggio 1: aggiungere un'estensione del servizio di notifica

  1. In Xcode, fai clic su File > Nuovo > Destinazione...
  2. Un modale presenterà un elenco di possibili obiettivi; scorri verso il basso o utilizza il filtro per selezionare Estensione del servizio di notifica . Fare clic su Avanti .
  3. Aggiungi un nome di prodotto (usa "ImageNotification" per seguire questo tutorial), imposta la lingua su Objective-C e fai clic su Fine .
  4. Abilita lo schema facendo clic su Attiva .

Passaggio 2: aggiungi il target al file pod

Assicurati che la tua nuova estensione abbia accesso al pod Firebase/Messaging aggiungendola nel Podfile:

  1. Dal Navigatore, apri il Podfile: Pods > Podfile

  2. Scorri fino alla fine del 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
    
  3. Installa o aggiorna i tuoi pod utilizzando pod install dalla directory ios o macos .

Passaggio 3: utilizzare l'helper dell'estensione

A questo punto, tutto dovrebbe ancora funzionare normalmente. Il passaggio finale consiste nell'invocare l'helper dell'estensione.

  1. Dal navigatore, seleziona la tua estensione ImageNotification

  2. Aprire il file NotificationService.m .

  3. Nella parte superiore del file, importa FirebaseMessaging.h subito dopo NotificationService.h come mostrato di seguito.

    Sostituisci il contenuto di NotificationService.m con:

    #import "NotificationService.h"
    #import "FirebaseMessaging.h"
    #import "FirebaseAuth.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 ()
    
    @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
    @property (nonatomic, strong) 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: aggiungere l'immagine al payload

Nel tuo payload di notifica, ora puoi aggiungere un'immagine. Consulta la documentazione iOS su come creare una richiesta di invio . Tieni presente che una dimensione massima dell'immagine di 300 KB viene applicata dal dispositivo.