En fonction de l'état d'un appareil, les messages entrants sont traités différemment. Pour comprendre ces scénarios et comment intégrer FCM à votre propre application, il est important de déterminer d'abord les différents états dans lesquels un appareil peut se trouver :
État | Description |
---|---|
Premier plan | Lorsque l'application est ouverte, visible et en cours d'utilisation. |
Contexte | Lorsque l'application est ouverte, mais en arrière-plan (réduite). Cela se produit généralement lorsque l'utilisateur a appuyé sur le bouton "Accueil" de l'appareil, a basculé vers une autre application à l'aide du sélecteur d'applications 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. |
Avant que l'application puisse recevoir des charges utiles de messages via FCM, certaines conditions préalables doivent être remplies :
- L'application doit avoir été ouverte au moins une fois (pour permettre l'enregistrement auprès de FCM).
- Sur iOS, si l'utilisateur fait glisser l'application hors du sélecteur d'applications, il doit la rouvrir manuellement pour que les messages en arrière-plan recommencent à fonctionner.
- Sur Android, si l'utilisateur force l'arrêt de l'application depuis les paramètres de l'appareil, il doit la rouvrir manuellement pour que les messages fonctionnent.
- Sur le Web, vous devez avoir demandé un jeton (à l'aide de
getToken()
) avec votre certificat de notification Web push.
Demander l'autorisation de recevoir des messages
Sur iOS, macOS, le Web et Android 13 (ou version ultérieure), vous devez d'abord demander l'autorisation de l'utilisateur avant de pouvoir recevoir des charges utiles FCM sur votre appareil.
Le package firebase_messaging
fournit une API simple pour demander l'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'objectif de chaque autorisation.
Pour commencer, appelez la méthode depuis votre application (sur iOS, une fenêtre modale native s'affiche ; sur le Web, le flux de l'API native du navigateur est 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
: utilisateur auquel l'autorisation a été accordée.denied
: l'utilisateur dont l'autorisation a été refusée.notDetermined
: l'utilisateur n'a pas encore choisi d'accorder ou non l'autorisation.provisional
: utilisateur auquel une autorisation provisoire a été accordée
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 l'appareil compris, votre application peut désormais 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
, qui détaille diverses informations sur la charge utile, telles que son origine, son ID unique, l'heure d'envoi, si elle contenait une notification, etc. Étant donné que le message a été récupéré alors que votre application est au premier plan, vous pouvez accéder directement à l'état et au contexte de votre application Flutter.
Messages au premier plan et de notification
Par défaut, les messages de notification qui arrivent lorsque l'application est au premier plan n'affichent pas de notification visible, que ce soit sur Android ou iOS. Il est toutefois possible de remplacer ce comportement :
- Sur Android, vous devez créer un canal de notification "Haute priorité".
- Sur iOS, vous pouvez modifier 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.
Plates-formes Apple et Android
Gérez les messages en arrière-plan en enregistrant un gestionnaire onBackgroundMessage
. Lorsqu'un message est reçu, un isolate est généré (Android uniquement, iOS/macOS ne nécessitent pas d'isolate distinct), ce qui vous permet de gérer les messages même lorsque votre application n'est pas en cours d'exécution.
Voici quelques points à retenir concernant votre gestionnaire de messages en arrière-plan :
- Il ne doit pas s'agir d'une fonction anonyme.
- Il doit s'agir d'une fonction de premier niveau (par exemple, pas une méthode de classe qui nécessite une initialisation).
- Lorsque vous utilisez Flutter 3.3.0 ou une version ulté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 de l'élimination du code mort 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 isolate en dehors du contexte de votre application, il n'est pas possible de mettre à jour l'état de l'application ni d'exécuter une logique ayant un impact sur l'UI. Vous pouvez toutefois effectuer des opérations logiques telles que des requêtes HTTP, des opérations d'E/S (par exemple, la mise à jour du stockage local), communiquer avec d'autres plug-ins, etc.
Il est également recommandé de terminer 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 l'OS. Si les tâches s'exécutent pendant plus de 30 secondes, l'appareil peut arrêter automatiquement le processus.
Web
Sur le Web, écrivez un service worker JavaScript 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 fichier dans votre répertoire web
et nommez-le 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);
});
Le fichier doit importer les SDK App et Messaging, initialiser Firebase et exposer la variable messaging
.
Ensuite, le nœud de calcul doit être enregistré. Dans le fichier index.html
, enregistrez le nœud de calcul en modifiant la balise <script>
qui amorce 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>
Si vous utilisez toujours l'ancien système de modèles, vous pouvez enregistrer le worker en modifiant la balise <script>
qui amorce Flutter comme suit :
<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>
Redémarrez ensuite votre application Flutter. Le worker sera enregistré et tous les messages en arrière-plan seront gérés via ce fichier.
Gérer les interactions
Les notifications étant des repères visuels, les utilisateurs ont tendance à interagir avec elles (en appuyant dessus). Le comportement par défaut sur Android et iOS consiste à ouvrir l'application. Si l'application est arrêtée, elle sera démarrée. Si elle est en arrière-plan, elle sera mise au premier plan.
En fonction du contenu d'une notification, vous pouvez gérer l'interaction de l'utilisateur lorsque l'application s'ouvre. Par exemple, si un nouveau message de chat est envoyé via une notification et que l'utilisateur appuie dessus, vous pouvez ouvrir la conversation spécifique lorsque l'application s'ouvre.
Le package firebase-messaging
propose deux façons de gérer cette interaction :
getInitialMessage()
: si l'application est ouverte à partir d'un état terminé, unFuture
contenant unRemoteMessage
sera renvoyé. Une fois utilisé, leRemoteMessage
sera supprimé.onMessageOpenedApp
:Stream
qui publie unRemoteMessage
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 expérience utilisateur fluide. L'exemple de code ci-dessous montre comment procéder :
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 à l'aide d'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égrer des chaînes localisées dans votre application et utiliser les paramètres régionaux natifs du système d'exploitation
Voici comment utiliser la deuxième méthode :
Android
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>
Spécifiez les messages traduits dans le répertoire
values-language
. Par exemple, spécifiez les messages en français dansresources/values-fr/strings.xml
:<string name="notification_title">Bonjour le monde</string> <string name="notification_message">C'est un message</string>
Dans la charge utile du serveur, au lieu d'utiliser les clés
title
,message
etbody
, utiliseztitle_loc_key
etbody_loc_key
pour votre message localisé, et définissez-les sur l'attributname
du message que vous souhaitez afficher.La charge utile du message se présente comme suit :
{ "android": { "notification": { "title_loc_key": "notification_title", "body_loc_key": "notification_message" } } }
iOS
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";
Spécifiez les messages traduits dans le répertoire
language.lproj
. Par exemple, spécifiez les messages en français dansfr.lproj/Localizable.strings
:"NOTIFICATION_TITLE" = "Bonjour le monde"; "NOTIFICATION_MESSAGE" = "C'est un message";
La charge utile du message se présente comme suit :
{ "apns": { "payload": { "alert": { "title-loc-key": "NOTIFICATION_TITLE", "loc-key": "NOTIFICATION_MESSAGE" } } } }
Activer l'exportation des données de diffusion des messages
Vous pouvez exporter les données de vos messages vers BigQuery pour les analyser plus en détail. BigQuery vous permet d'analyser les données à l'aide de BigQuery SQL, de les exporter vers un autre fournisseur de cloud ou de les utiliser pour vos modèles de ML personnalisés. Une exportation vers BigQuery inclut toutes les données disponibles pour les messages, quel que soit leur type ou qu'ils aient été envoyés via l'API ou le compositeur de notifications.
Pour activer l'exportation, suivez d'abord les étapes décrites ici, puis procédez comme suit :
Android
Vous pouvez utiliser le code suivant :
await FirebaseMessaging.instance.setDeliveryMetricsExportToBigQuery(true);
iOS
Pour iOS, vous devez remplacer AppDelegate.m
par 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
Web
Pour le Web, vous devez modifier votre service worker afin d'utiliser la version 9 du SDK.
La version 9 doit être groupée. Vous devez donc utiliser un bundler comme esbuild
, par exemple, pour que le service worker fonctionne.
Pour savoir comment procéder, consultez l'exemple d'application.
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
pour exporter la nouvelle version de votre service worker vers le dossier web
.
Afficher des images dans les notifications sur iOS
Sur les appareils Apple, pour que les notifications FCM entrantes affichent des images à partir 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 Firebase par téléphone, vous devez ajouter le pod Firebase Auth à votre Podfile.
Étape 1 : Ajoutez une extension de service de notification
- Dans Xcode, cliquez sur File > New > Target… (Fichier > Nouveau > Cible…).
- Une fenêtre modale affichera une liste des cibles possibles. Faites défiler la liste ou utilisez le filtre pour sélectionner Notification Service Extension (Extension de service de notification). Cliquez sur Suivant.
- Ajoutez un nom de produit (utilisez "ImageNotification" pour suivre ce tutoriel), sélectionnez
Swift
ouObjective-C
, puis cliquez sur Terminer. - Activez le schéma en cliquant sur Activer.
Étape 2 : Ajoutez une cible au fichier Podfile
Swift
Assurez-vous que votre nouvelle extension a accès au package Swift FirebaseMessaging
en l'ajoutant à votre cible Runner
:
Dans le navigateur, ajoutez le SDK des plates-formes Firebase pour Apple : File > Add Package Dependencies... (Fichier > Ajouter des dépendances de package…).
Recherchez ou saisissez l'URL du package :
https://github.com/firebase/firebase-ios-sdk
Ajouter au projet
Runner
: Ajouter un packageSélectionnez FirebaseMessaging et ajoutez-le à la cible ImageNotification : Add Package (Ajouter un package).
Objective-C
Assurez-vous que votre nouvelle extension a accès au pod Firebase/Messaging
en l'ajoutant au Podfile :
Dans le navigateur, ouvrez le fichier Podfile : Pods > Podfile.
Faites défiler la page jusqu'en 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
Installez ou mettez à jour vos pods à l'aide de
pod install
à partir du répertoireios
oumacos
.
Étape 3 : Utilisez l'assistant d'extension
À ce stade, tout devrait encore fonctionner normalement. La dernière étape consiste à appeler l'application d'assistance de l'extension.
Swift
Dans le navigateur, sélectionnez votre extension ImageNotification.
Ouvrez le fichier
NotificationService.swift
.Remplacez le contenu de
NotificationService.swift
par :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
Dans le navigateur, sélectionnez votre extension ImageNotification.
Ouvrez le fichier
NotificationService.m
.En haut du fichier, importez
FirebaseMessaging.h
juste aprèsNotificationService.h
, comme indiqué ci-dessous.Remplacez le contenu de
NotificationService.m
par :#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
Étape 4 : Ajouter l'image à la charge utile
Vous pouvez désormais ajouter une image à la charge utile de votre notification. Consultez la documentation iOS sur la création d'une requête d'envoi. N'oubliez pas que la taille maximale des images est de 300 Ko.