Recevoir des messages dans une application Flutter

Selon l'état d'un appareil, les messages entrants sont traités différemment. Pour comprendre ces scénarios et savoir comment intégrer FCM dans votre propre application, il est d'abord important d'établir les différents états dans lesquels un appareil peut se trouver :

État Description
Premier plan Lorsque l'application est ouverte, visible et utilisée.
Arrière-plan Lorsque l'application est ouverte, mais en arrière-plan (minimisée). Cela se produit généralement lorsque l'utilisateur a appuyé sur le bouton « accueil » de l'appareil, est passé à une autre application à l'aide du sélecteur d'application ou a ouvert l'application dans un autre onglet (Web).
Terminé Lorsque l'appareil est verrouillé ou que l'application n'est pas en cours d'exécution.

Quelques conditions préalables doivent être remplies avant que l'application puisse recevoir des charges utiles de message via FCM :

  • L'application doit avoir été ouverte au moins une fois (pour permettre l'inscription auprès de la FCM).
  • Sur iOS, si l'utilisateur fait glisser l'application du sélecteur d'applications, elle doit être rouverte manuellement pour que les messages en arrière-plan recommencent à fonctionner.
  • Sur Android, si l'utilisateur force l'utilisateur à quitter l'application à partir des paramètres de l'appareil, celle-ci doit être rouverte manuellement pour que les messages commencent à fonctionner.
  • Sur le Web, vous devez avoir demandé un token (en utilisant getToken() ) avec votre certificat web push.

Demander l'autorisation de recevoir des messages

Sur iOS, macOS, Web et Android 13 (ou version ultérieure), avant que les charges utiles FCM puissent être reçues sur votre appareil, vous devez d'abord demander l'autorisation de l'utilisateur.

Le package firebase_messaging fournit une API simple pour demander une autorisation via la méthode requestPermission . Cette API accepte un certain nombre d'arguments nommés qui définissent le type d'autorisations que vous souhaitez demander, par exemple si la messagerie contenant des charges utiles de notification peut déclencher un son ou lire des messages via Siri. Par défaut, la méthode demande des autorisations par défaut raisonnables. L'API de référence fournit une documentation complète sur l'utilité de chaque autorisation.

Pour commencer, appelez la méthode depuis votre application (sur iOS un modal natif sera affiché, sur web le flux API natif du navigateur sera déclenché) :

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 propriété authorizationStatus de l'objet NotificationSettings renvoyé par la requête peut être utilisée pour déterminer la décision globale de l'utilisateur :

  • authorized : l'utilisateur a obtenu l'autorisation.
  • denied : l'utilisateur a refusé l'autorisation.
  • notDetermined : l'utilisateur n'a pas encore choisi d'accorder ou non l'autorisation.
  • provisional : l'utilisateur a obtenu une autorisation provisoire

Les autres propriétés sur NotificationSettings indiquent si une autorisation spécifique est activée, désactivée ou non prise en charge sur l'appareil actuel.

Une fois l'autorisation accordée et les différents types d'état de l'appareil compris, votre application peut maintenant commencer à gérer les charges utiles FCM entrantes.

Gestion des messages

En fonction de l'état actuel de votre application, les charges utiles entrantes de différents types de messages nécessitent différentes implémentations pour les gérer :

Messages au premier plan

Pour gérer les messages lorsque votre application est au premier plan, écoutez le flux 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}');
  }
});

Le flux contient un RemoteMessage , détaillant diverses informations sur la charge utile, telles que son origine, son identifiant unique, l'heure d'envoi, si elle contenait une notification, etc. Le message ayant été récupéré alors que votre application était au premier plan, vous pouvez accéder directement à l'état et au contexte de votre application Flutter.

Messages de premier plan et de notification

Les messages de notification qui arrivent alors que l'application est au premier plan n'afficheront pas de notification visible par défaut, tant sur Android que sur iOS. Il est cependant possible de remplacer ce comportement :

  • Sur Android, vous devez créer un canal de notification « Haute priorité ».
  • Sur iOS, vous pouvez mettre à jour les options de présentation de l'application.

Messages d'arrière-plan

Le processus de gestion des messages en arrière-plan est différent sur les plateformes natives (Android et Apple) et Web.

Plateformes Apple et Android

Gérez les messages en arrière-plan en enregistrant un gestionnaire onBackgroundMessage . Lorsque des messages sont reçus, un isolat est généré (Android uniquement, iOS/macOS ne nécessite pas d'isolat séparé) vous permettant de gérer les messages même lorsque votre application n'est pas en cours d'exécution.

Il y a quelques points à garder à l'esprit concernant votre gestionnaire de messages en arrière-plan :

  1. Il ne doit pas s'agir d'une fonction anonyme.
  2. Il doit s'agir d'une fonction de niveau supérieur (par exemple, pas d'une méthode de classe nécessitant une initialisation).
  3. Lorsque vous utilisez Flutter version 3.3.0 ou supérieure, le gestionnaire de messages doit être annoté avec @pragma('vm:entry-point') juste au-dessus de la déclaration de fonction (sinon, il peut être supprimé lors du tremblement de l'arborescence pour le mode release).
@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());
}

Étant donné que le gestionnaire s'exécute dans son propre isolat en dehors du contexte de vos applications, il n'est pas possible de mettre à jour l'état de l'application ou d'exécuter une logique ayant un impact sur l'interface utilisateur. Vous pouvez cependant exécuter des logiques telles que des requêtes HTTP, effectuer des opérations d'E/S (par exemple mettre à jour le stockage local), communiquer avec d'autres plugins, etc.

Il est également recommandé de terminer votre logique le plus tôt possible. L'exécution de tâches longues et intensives a un impact sur les performances de l'appareil et peut entraîner l'arrêt du processus par le système d'exploitation. Si les tâches s'exécutent pendant plus de 30 secondes, l'appareil peut automatiquement arrêter le processus.

la toile

Sur le Web, écrivez un JavaScript Service Worker qui s'exécute en arrière-plan. Utilisez le service worker pour gérer les messages en arrière-plan.

Pour commencer, créez un nouveau fichier dans votre répertoire web et appelez-le 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);
});

Le fichier doit importer à la fois les SDK d'application et de messagerie, initialiser Firebase et exposer la variable messaging .

Ensuite, le travailleur doit être enregistré. Dans le fichier d'entrée, une fois le fichier main.dart.js chargé, enregistrez votre travailleur :

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

Redémarrez ensuite votre application Flutter. Le travailleur sera enregistré et tous les messages d'arrière-plan seront traités via ce fichier.

Gestion des interactions

Étant donné que les notifications sont un signal visible, il est courant que les utilisateurs interagissent avec elles (en appuyant sur). Le comportement par défaut sur Android et iOS consiste à ouvrir l’application. Si l'application est terminée, elle sera démarrée ; s'il est en arrière-plan, il sera mis au premier plan.

En fonction du contenu d'une notification, vous souhaiterez peut-être gérer l'interaction de l'utilisateur à l'ouverture de l'application. Par exemple, si un nouveau message de discussion est envoyé via une notification et que l'utilisateur appuie dessus, vous souhaiterez peut-être ouvrir la conversation spécifique à l'ouverture de l'application.

Le package firebase-messaging propose deux manières de gérer cette interaction :

  • getInitialMessage() : Si l'application est ouverte à partir d'un état terminé, un Future contenant un RemoteMessage sera renvoyé. Une fois consommé, le RemoteMessage sera supprimé.
  • onMessageOpenedApp : un Stream qui publie un RemoteMessage lorsque l'application est ouverte en arrière-plan.

Il est recommandé de gérer les deux scénarios pour garantir une UX fluide à vos utilisateurs. L'exemple de code ci-dessous explique comment cela peut être réalisé :

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 façon dont vous gérez l’interaction dépend de la configuration de votre application. L'exemple ci-dessus montre une illustration de base utilisant un StatefulWidget.

Localiser les messages

Vous pouvez envoyer des chaînes localisées de deux manières différentes :

  • Stockez la langue préférée de chacun de vos utilisateurs sur votre serveur et envoyez des notifications personnalisées pour chaque langue
  • Intégrez des chaînes localisées dans votre application et utilisez les paramètres régionaux natifs du système d'exploitation.

Voici comment utiliser la deuxième méthode :

Android

  1. Spécifiez vos messages dans la langue par défaut dans resources/values/strings.xml :

    <string name="notification_title">Hello world</string>
    <string name="notification_message">This is a message</string>
    
  2. Spécifiez les messages traduits dans le répertoire values- language . Par exemple, spécifiez les messages en français dans resources/values-fr/strings.xml :

    <string name="notification_title">Bonjour le monde</string>
    <string name="notification_message">C'est un message</string>
    
  3. Dans la charge utile du serveur, au lieu d'utiliser les clés title , message et body , utilisez title_loc_key et body_loc_key pour votre message localisé et définissez-les sur l'attribut name du message que vous souhaitez afficher.

    La charge utile du message ressemblerait à ceci :

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

IOS

  1. Spécifiez vos messages dans la langue par défaut dans Base.lproj/Localizable.strings :

    "NOTIFICATION_TITLE" = "Hello World";
    "NOTIFICATION_MESSAGE" = "This is a message";
    
  2. Spécifiez les messages traduits dans le répertoire language .lproj . Par exemple, spécifiez les messages en français dans fr.lproj/Localizable.strings :

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

    La charge utile du message ressemblerait à ceci :

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

Activer l'exportation des données de livraison des messages

Vous pouvez exporter les données de vos messages dans BigQuery pour une analyse plus approfondie. BigQuery vous permet d'analyser les données à l'aide de BigQuery SQL, de les exporter vers un autre fournisseur de cloud ou d'utiliser les données pour vos modèles ML personnalisés. Une exportation vers BigQuery inclut toutes les données disponibles pour les messages, quel que soit le type de message ou si le message est envoyé via l'API ou l'éditeur de notifications.

Pour activer l'exportation, suivez d'abord les étapes décrites ici , puis suivez ces instructions :

Android

Vous pouvez utiliser le code suivant :

await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);

IOS

Pour iOS, vous devez modifier AppDelegate.m avec le contenu suivant.

#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

la toile

Pour le Web, vous devez changer de service worker afin d'utiliser la version v9 du SDK. La version v9 doit être regroupée, vous devez donc utiliser un bundler comme esbuild par exemple pour faire fonctionner le service worker. Consultez l’exemple d’application pour voir comment y parvenir.

Une fois que vous avez migré vers le SDK v9, vous pouvez utiliser le code suivant :

import {
  experimentalSetDeliveryMetricsExportedToBigQueryEnabled,
  getMessaging,
} from 'firebase/messaging/sw';
...

const messaging = getMessaging(app);
experimentalSetDeliveryMetricsExportedToBigQueryEnabled(messaging, true);

N'oubliez pas de lancer yarn build afin d'exporter la nouvelle version de votre service Worker vers le dossier web .

Afficher les images dans les notifications sur iOS

Sur les appareils Apple, pour que les notifications FCM entrantes affichent les images de la charge utile FCM, vous devez ajouter une extension de service de notification supplémentaire et configurer votre application pour l'utiliser.

Si vous utilisez l'authentification téléphonique Firebase, vous devez ajouter le pod Firebase Auth à votre Podfile.

Étape 1 : Ajouter une extension de service de notification

  1. Dans Xcode, cliquez sur Fichier > Nouveau > Cible...
  2. Un modal présentera une liste de cibles possibles ; faites défiler vers le bas ou utilisez le filtre pour sélectionner Extension du service de notification . Cliquez sur Suivant .
  3. Ajoutez un nom de produit (utilisez "ImageNotification" pour suivre ce didacticiel), définissez le langage sur Objective-C et cliquez sur Terminer .
  4. Activez le schéma en cliquant sur Activer .

Étape 2 - Ajouter une cible au Podfile

Assurez-vous que votre nouvelle extension a accès au pod Firebase/Messaging en l'ajoutant dans le Podfile :

  1. Depuis le navigateur, ouvrez le Podfile : Pods > Podfile

  2. Faites défiler vers le bas du fichier et ajoutez :

    target 'ImageNotification' do
      use_frameworks!
      pod 'Firebase/Auth' # Add this line if you are using FirebaseAuth phone authentication
      pod 'Firebase/Messaging'
    end
    
  3. Installez ou mettez à jour vos pods à l'aide pod install à partir du répertoire ios ou macos .

Étape 3 - Utilisez l'assistant d'extension

À ce stade, tout devrait toujours fonctionner normalement. La dernière étape consiste à appeler l’assistant d’extension.

  1. Depuis le navigateur, sélectionnez votre extension ImageNotification

  2. Ouvrez le fichier NotificationService.m .

  3. En haut du fichier, importez FirebaseMessaging.h juste après NotificationService.h comme indiqué ci-dessous.

    Remplacez le contenu de NotificationService.m par :

    #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
    

Étape 4 : Ajouter l'image à la charge utile

Dans votre charge utile de notification, vous pouvez désormais ajouter une image. Consultez la documentation iOS pour savoir comment créer une demande d'envoi . Gardez à l’esprit qu’une taille d’image maximale de 300 Ko est imposée par l’appareil.