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).
Résilié 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 à la FCM).
  • Sur iOS, si l'utilisateur fait glisser l'application depuis le 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 la fermeture de l'application à partir des paramètres de l'appareil, elle doit être rouverte manuellement pour que les messages commencent à fonctionner.
  • Sur le Web, vous devez avoir demandé un jeton (en utilisant getToken() ) avec votre certificat push Web.

Demander l'autorisation de recevoir des messages (Apple et Web)

Sur iOS, macOS et Web, 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'objet de chaque autorisation.

Pour commencer, appelez la méthode depuis votre application (sur iOS un modal natif sera affiché, sur le 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 accordé 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 accordé une autorisation provisoire

Les autres propriétés de 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 périphérique compris, votre application peut maintenant commencer à gérer les charges utiles FCM entrantes.

Traitement 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 de premier plan

Pour gérer les messages pendant que 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, l'identifiant unique, l'heure d'envoi, s'il contenait une notification, etc. Comme le message a é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, sur Android et iOS. Il est cependant possible d'outrepasser ce comportement :

  • Sur Android, vous devez créer un canal de notification "High Priority".
  • Sur iOS, vous pouvez mettre à jour les options de présentation de l'application.

Messages en arrière-plan

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

Plateformes Apple et Android

Gérez les messages d'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 une méthode de classe nécessitant une initialisation).
  3. Lors de l'utilisation de la version 3.3.0 ou supérieure de Flutter, le gestionnaire de messages doit être annoté avec @pragma('vm:entry-point') juste au-dessus de la déclaration de la fonction (sinon, il peut être supprimé lors de l'agitation de l'arborescence pour le mode de publication).
@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 impactant l'interface utilisateur. Vous pouvez cependant effectuer des opérations 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 compléter votre logique dès que 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 en 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 lancée ; s'il est en arrière-plan, il sera ramené au premier plan.

Selon le contenu d'une notification, vous souhaiterez peut-être gérer l'interaction de l'utilisateur lors de l'ouverture de l'application. Par exemple, si un nouveau message de chat 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 à partir d'un état d'arrière-plan.

Il est recommandé de gérer les deux scénarios pour garantir une UX fluide pour vos utilisateurs. L'exemple de code ci-dessous décrit 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 :

  • Enregistrez 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 de 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 de 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 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 remise des messages

Vous pouvez exporter vos données de message 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 de 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 groupée, vous devez donc utiliser un bundle comme esbuild par exemple pour faire fonctionner le service worker. Voir 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 d'exécuter 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 par téléphone Firebase, vous devez ajouter le module 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 la langue sur Objective-C, puis 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 de pod install à partir du répertoire ios ou macos .

Étape 3 - Utiliser l'assistant d'extension

À ce stade, tout devrait encore 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 maintenant 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.