Accueil / Articles

Notifications React Native : Permissions, canaux et cycle de vie FCM

Apprenez comment les permissions, les canaux Android, les tokens FCM ainsi que les gestionnaires d’état en premier plan, en arrière-plan et à l’arrêt s’intègrent dans un système de notifications React Native avec Notifee.

Éditeur : REACTAPP.TOP19 min de lecture4332 mots

Sources: React Native Notifications & Permissions: A Complete Guide for Android and iOS

Pour un utilisateur, une notification est une ligne de texte telle que « Votre commande a été expédiée ». Pour l’application qui la envoie, cette ligne dépend des permissions en temps de exécution, des canaux de notification Android, des tokens du dispositif, de Firebase Cloud Messaging (FCM), des gestionnaires en arrière-plan ainsi que d’un ensemble de différences entre les plateformes Android et iOS. Une erreur à n’importe quel niveau entraîne des notifications qui ne parviennent jamais ou qui affichent l’écran incorrect. Ce guide explique chaque niveau en utilisant Notifee et React Native Firebase afin que vous puissiez créer un système de notification qui se comporte de manière prévisible dans tous les états de l’application.

Notifications locales et notifications push

Notifications locales

Une notification locale est créée sur le dispositif par l’application elle-même, sans intervention d’un serveur. Un rappel de réunion en est un exemple typique :

Reminder
Your meeting starts in 10 minutes.

L’application peut afficher ou planifier des notifications hors ligne. Notifee propose une API React Native qui s’appuie sur les systèmes de notification natifs des deux plateformes.

Notifications push

Une notification push démarre sur un serveur. Votre backend transmet le message à FCM, qui le livre au dispositif, et l’application React Native réagit en conséquence :

Your Backend
     │
     ▼
Firebase Cloud Messaging
     │
     ▼
Android / iOS Device
     │
     ▼
React Native Application

FCM fait la distinction entre les messages de notification (que le système peut afficher pour vous) et les messages de données (contenus clé-valeur arbitraires destinés à votre code), et il peut s’adresser à un seul dispositif ou à un groupe de dispositifs via des sujets.

Les permissions font partie de l’état de votre application

L’installation d’une application ne signifie plus nécessairement qu’elle pourra afficher des notifications. Depuis Android 13, POST_NOTIFICATIONS est une autorisation en temps de exécution, et les applications destinées à Android 13 ou ultérieures doivent en demander l’autorisation avant d’afficher quoi que ce soit. iOS exige toujours une autorisation explicite de l’utilisateur. Considérez cette autorisation comme un état de l’application qui peut changer, et non comme une étape de configuration unique.

Un flux logique consiste à vérifier l’état, à expliquer les avantages, puis à demander l’autorisation ; quelle que soit la réponse, l’application peut continuer :

Application starts
       │
       ▼
Check notification permission
       │
       ├── Granted ───────► Continue
       │
       └── Not granted
                │
                ▼
        Explain the benefit
                │
                ▼
        Request permission
                │
          ┌─────┴─────┐
          ▼           ▼
       Granted      Denied
          │           │
          ▼           ▼
     Notifications   Continue

Installation des bibliothèques

Notifee s’occupe de l’affichage des notifications :

npm install @notifee/react-native

React Native Firebase fournit le module principal de l’application ainsi que le module de messagerie pour les notifications en temps réel :

npm install @react-native-firebase/app
npm install @react-native-firebase/messaging

Sur iOS, installez ensuite les dépendances natives CocoaPods :

cd ios
pod install
cd ..

La configuration native varie en fonction de la version de React Native et de l’organisation du projet. De plus, les notifications push pour iOS nécessitent des identifiants APNs dans Firebase ainsi que la fonctionnalité de push activée dans Xcode. Consultez toujours la documentation actuelle de chaque bibliothèque.

Affichage d’une première notification native

L’objectif du premier exemple est d’afficher une notification ayant ce titre et ce corps :

New Message
You have received a new message.

Commencez par importer Notifee ainsi que l’enum d’importance utilisé pour les canaux Android :

import notifee, {
  AndroidImportance,
} from '@notifee/react-native';

La fonction ci-dessous effectue trois actions dans l’ordre : elle demande l’autorisation, crée (ou réutilise) un canal Android nommé default, puis affiche la notification sur ce canal. L’élément pressAction ayant pour identifiant default indique à Android d’ouvrir l’application lorsqu’on clique sur la notification ; sans cela, un clic n’a aucun effet.

export async function displayNotification() {
  // Request permission
  await notifee.requestPermission();  // Create Android notification channel
  const channelId = await notifee.createChannel({
    id: 'default',
    name: 'Default Notifications',
    importance: AndroidImportance.DEFAULT,
  });  // Display notification
  await notifee.displayNotification({
    title: 'New Message',
    body: 'You have received a new message.',    android: {
      channelId,
      pressAction: {
        id: 'default',
      },
    },
  });
}

Appeler createChannel à plusieurs reprises est sans risque ; un canal existant renvoie simplement son identifiant. Les canaux sont obligatoires sur Android 8.0 (API 26) et versions ultérieures. Déclenchez la fonction depuis un bouton :

<Button
  title="Show Notification"
  onPress={displayNotification}
/>

Demander l’autorisation Android 13

L’autorisation Android porte ce nom complet :

android.permission.POST_NOTIFICATIONS

React Native la expose sous forme de constante dans PermissionsAndroid :

PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS

Cette autorisation doit également être déclarée avec uses-permission dans AndroidManifest.xml, sinon la demande est refusée silencieusement. Commencez par les importations :

import {
  PermissionsAndroid,
  Platform,
} from 'react-native';

L’aide-mémoire renvoie true immédiatement sur iOS et sur les versions Android inférieures à 13 (niveau API 33), où les notifications sont autorisées par défaut. Sinon, il affiche la boîte de dialogue système et indique si le résultat était GRANTED :

export async function requestAndroidNotificationPermission() {
  if (Platform.OS !== 'android') {
    return true;
  }  if (Platform.Version < 33) {
    return true;
  }  const result = await PermissionsAndroid.request(
    PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
  );  return result === PermissionsAndroid.RESULTS.GRANTED;
}

L’appeler depuis une interface :

const granted =
  await requestAndroidNotificationPermission();

et prendre une décision en fonction du résultat :

if (granted) {
  console.log('Notifications allowed');
} else {
  console.log('Notifications denied');
}

Android peut retourner trois résultats significatifs :

GRANTED
DENIED
NEVER_ASK_AGAIN

NEVER_ASK_AGAIN mérite une attention particulière. Après plusieurs refus successifs, le système cesse d’afficher la boîte de dialogue, et seules les paramètres du système peuvent modifier cette réponse.

Poser la question au bon moment

Lancer la demande système dès la première interface est une erreur courante et coûteuse. Un nouvel utilisateur qui voit immédiatement

"Allow MyApp to send notifications?"

n’a pas de contexte et refusera probablement. Expliquez d’abord, par exemple sur une page de bienvenue :

Welcome to MyApp

suivie d’une carte indiquant à quoi servent les notifications, avec un bouton :

Enable notifications to receive:
✓ New messages
✓ Order updates
✓ Important alerts[Enable Notifications]

Seul ce clic déclenche la boîte de dialogue du système, de sorte que la demande répond à un choix de l’utilisateur plutôt qu’à une interruption.

Autorisation iOS avec Notifee

Sur iOS, l’utilisateur doit autoriser explicitement la livraison des notifications. Notifee indique le résultat via AuthorizationStatus:

import notifee, {
  AuthorizationStatus,
} from '@notifee/react-native';

L’aide ci-dessous considère deux statuts comme des succès. AUTHORIZED signifie une autorisation complète. PROVISIONAL correspond au mode silencieux d’iOS, où les notifications sont envoyées directement dans le Centre de notifications sans perturber l’utilisateur, qui pourra ensuite les conserver ou les désactiver. Tous les autres statuts sont considérés comme un refus :

export async function requestIOSNotificationPermission() {
  const settings = await notifee.requestPermission();  if (
    settings.authorizationStatus ===
    AuthorizationStatus.AUTHORIZED
  ) {
    console.log('Notifications authorized');
    return true;
  }  if (
    settings.authorizationStatus ===
    AuthorizationStatus.PROVISIONAL
  ) {
    console.log('Notifications provisionally authorized');
    return true;
  }  console.log('Notifications denied');  return false;
}

La demande iOS peut inclure une série d’options :

Alerts
Sounds
Badges
CarPlay
Announcements
Provisional notifications

Ne demandez que ce dont vous avez besoin ; par exemple, l’autorisation provisoire évite complètement la demande d’autorisation.

Une seule fonction d’autorisation pour toute l’application

Au lieu de disperser les vérifications sur différentes pages, utilisez un seul module de service :

src/
 ├── services/
 │    └── notificationService.ts
 ├── screens/
 ├── components/
 └── App.tsx

Le fichier de service :

// notificationService.ts

Il combine les chemins des deux plateformes. Sur Android, il effectue une vérification de version ainsi qu’une demande en temps réel ; sur iOS, il utilise Notifee et accepte à la fois les statuts autorisés et provisoires :

import {
  Platform,
  PermissionsAndroid,
} from 'react-native';import notifee, {
  AuthorizationStatus,
} from '@notifee/react-native';export async function requestNotificationPermission() {
  if (Platform.OS === 'android') {
    if (Platform.Version < 33) {
      return true;
    }    const result = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
    );    return result === PermissionsAndroid.RESULTS.GRANTED;
  }  const settings = await notifee.requestPermission();  return (
    settings.authorizationStatus ===
      AuthorizationStatus.AUTHORIZED ||
    settings.authorizationStatus ===
      AuthorizationStatus.PROVISIONAL
  );
}

Les écrans appellent ensuite une fonction :

const enabled = await requestNotificationPermission();

et agissent en fonction de la réponse :

if (enabled) {
  console.log('Ready for notifications');
}

Les versions récentes de Notifee peuvent également demander POST_NOTIFICATIONS sur Android 13 via requestPermission() ; consultez la documentation actuelle de Notifee si vous préférez une seule appel pour les deux plateformes.

Canaux de notification Android

Android 8.0 a introduit les canaux afin que les utilisateurs puissent contrôler indépendamment les catégories de notifications. Une application peut en exposer plusieurs :

MyApp
│
├── Messages
├── Orders
├── Promotions
└── Security

Chaque canal possède sa propre importance et son propre son. Les messages méritent généralement un niveau d’importance élevé afin de s’afficher en priorité :

const messageChannel = await notifee.createChannel({
  id: 'messages',
  name: 'Messages',
  importance: AndroidImportance.HIGH,
});

Lors des mises à jour de commande, on peut utiliser le niveau par défaut :

const orderChannel = await notifee.createChannel({
  id: 'orders',
  name: 'Orders',
  importance: AndroidImportance.DEFAULT,
});

Une notification indique alors le canal auquel elle appartient. L’appel est présenté en deux parties : d’abord le titre, puis le corps :

await notifee.displayNotification({
  title: 'New Message',
  body: 'You received a new message.',

ensuite les options Android qui font référence à l’ID du canal retourné par createChannel :

  android: {
    channelId: messageChannel,
  },
});

Les canaux s’adressent aux utilisateurs, et non seulement aux développeurs. Les utilisateurs peuvent silencer ou bloquer un canal dans les paramètres du système, et une fois un canal créé, l’application ne peut plus modifier son niveau d’importance de manière programmée ; c’est l’utilisateur qui prend cette décision. Choisissez avec soin, ou utilisez un nouvel ID de canal par la suite.

Une structure efficace en environnement de production sépare les canaux selon leur finalité :

notifications
│
├── messages
├── orders
├── payments
├── security
└── marketing

Un canal de marketing distinct permet aux utilisateurs de mettre en silence les promotions sans perdre les alertes de sécurité.

Le rôle de FCM

Pour le push, FCM se situe entre votre backend et chaque application installée :

┌─────────────────┐
             │   Your Backend  │
             └────────┬────────┘
                      │
                      ▼
             ┌─────────────────┐
             │      FCM        │
             └────────┬────────┘
                      │
              ┌───────┴───────┐
              ▼               ▼
        ┌──────────┐     ┌──────────┐
        │ Android  │     │   iOS    │
        └──────────┘     └──────────┘

Récupération du token de l’appareil

Chaque installation de l’application peut recevoir un token d’enregistrement FCM, et votre backend a besoin de ce token pour s’adresser à un appareil spécifique. Importez le module de messagerie :

import messaging from '@react-native-firebase/messaging';

et demandez le token :

export async function getFCMToken() {
  const token = await messaging().getToken();  console.log('FCM Token:', token);  return token;
}

Dans une application réelle, le token est envoyé à votre serveur immédiatement après avoir été obtenu :

const token = await messaging().getToken();
await fetch('https://api.example.com/device-token', {
  method: 'POST',  headers: {
    'Content-Type': 'application/json',
  },  body: JSON.stringify({
    token,
  }),
});

Votre backend stocke ensuite le token en lien avec l’utilisateur connecté et l’appareil :

User
  │
  ├── userId
  ├── deviceId
  └── fcmToken

Mise à jour des tokens

Les tokens changent, par exemple après une réinstallation ou un effacement des données de l’application. Écoutez les événements de mise à jour :

import messaging from '@react-native-firebase/messaging';

Le gestionnaire d’événements transmet chaque nouveau token et la fonction renvoie une fonction de désabonnement :

export function subscribeToTokenRefresh() {
  return messaging().onTokenRefresh(async token => {
    console.log('New FCM token:', token);    await sendTokenToBackend(token);
  });
}

Un outil minimal d’envoi de fichiers, présenté à nouveau en deux parties, effectue une requête POST JSON :

async function sendTokenToBackend(token: string) {
  await fetch('https://api.example.com/device-token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },    body: JSON.stringify({
      token,
    }),
  });
}

Les directives officielles de Firebase recommandent de gérer les changements de token et d’envoyer la nouvelle valeur vers votre serveur d’application dès qu’un changement a lieu. Du côté du serveur, il faut également supprimer les tokens que FCM déclare comme invalides, afin d’éviter l’accumulation de dispositifs obsolètes.

Les messages en premier plan ne s’affichent pas automatiquement

Le cas le plus déroutant concerne une notification push reçue alors que l’application est ouverte :

User
  │
  ▼
React Native App
  │
  ▼
FCM message

Écoutez les messages en premier plan :

import messaging from '@react-native-firebase/messaging';
const unsubscribe = messaging().onMessage(
  async remoteMessage => {
    console.log('Foreground message:', remoteMessage);
  },
);

Cependant, recevoir un message et afficher une notification système sont deux étapes distinctes. Lorsque l’application est en premier plan, FCM transmet le message à votre code sans afficher quoi que ce soit par lui-même. Pour afficher une notification, transmettez son contenu à Notifee :

import notifee from '@notifee/react-native';

Le gestionnaire ci-dessous s’assure que le canal existe, puis affiche la notification ; il recourt au texte par défaut lorsque le message ne contient pas de bloc notification (par exemple, un message ne contenant que des données) :

messaging().onMessage(async remoteMessage => {
  const channelId = await notifee.createChannel({
    id: 'messages',
    name: 'Messages',
  });  await notifee.displayNotification({
    title:
      remoteMessage.notification?.title ??
      'New Message',    body:
      remoteMessage.notification?.body ??
      'You have a new message.',    android: {
      channelId,
      pressAction: {
        id: 'default',
      },
    },
  });
});

Vous contrôlez la présentation, y compris le fait de l’omettre lorsque l’écran correspondant est déjà visible.

Messages en arrière-plan et état de fermeture

Lorsque l’application est en arrière-plan, son comportement change. Sur Android, les messages de notification FCM sont envoyés directement dans la barre des tâches sans déclencher votre callback en premier plan, et les messages combinant notification et données suivent leurs propres règles d’arrière-plan. Sur iOS, les messages en arrière-plan ne contenant que des données sont traités selon les meilleures capacités du système, ce qui peut entraîner des retards ou une limitation de leur transmission.

React Native Firebase fournit un gestionnaire dédié aux messages en arrière-plan :

import messaging from '@react-native-firebase/messaging';
messaging().setBackgroundMessageHandler(
  async remoteMessage => {
    console.log(
      'Background message:',
      remoteMessage,
    );
  },
);

Une erreur fréquente consiste à enregistrer ce gestionnaire à l’intérieur d’un composant React, comme dans le fragment suivant :

function App() {
  messaging().setBackgroundMessageHandler(...);
  return <Navigation />;
}

Un réveil en arrière-plan peut ne pas charger de composant, ce qui signifie que le gestionnaire n’existera jamais. Enregistrez-le au point d’entrée :

// index.js
import messaging from '@react-native-firebase/messaging';messaging().setBackgroundMessageHandler(
  async remoteMessage => {
    console.log(
      'Background message:',
      remoteMessage,
    );
  },
);

et n’enregistrez le composant racine qu’ensuite :

AppRegistry.registerComponent(
  appName,
  () => App,
);

Réagir aux clics sur les notifications

Un clic doit généralement amener l’utilisateur vers une destination spécifique :

Notification
     │
     ▼
User taps
     │
     ▼
Application opens
     │
     ▼
Order Details

Supposons que le message contienne un ID de commande dans ses données :

{
  "orderId": "12345"
}

Pour les applications reprises depuis l’arrière-plan, onNotificationOpenedApp est déclenché lorsque l’utilisateur clique sur une notification. La fonction de rappel lit l’ID depuis remoteMessage.data et peut naviguer à partir de là :

messaging().onNotificationOpenedApp(
  remoteMessage => {
    const orderId =
      remoteMessage.data?.orderId;
    if (orderId) {
      // Navigate to order details
      console.log(
        'Open order:',
        orderId,
      );
    }
  },
);

Lorsque l’application a été complètement fermée

Si l’application a été fermée, la séquence est la suivante :

App completely closed
        │
        ▼
User receives notification
        │
        ▼
User taps notification
        │
        ▼
App launches

Dans ce cas, onNotificationOpenedApp n’est pas déclenché. Il faut plutôt demander la notification qui a lancé l’application une seule fois lors du démarrage :

const remoteMessage =
  await messaging().getInitialNotification();
if (remoteMessage) {
  const orderId =
    remoteMessage.data?.orderId;  console.log(
    'App opened from notification:',
    orderId,
  );
}

Ces deux méthodes ne couvrent que les notifications affichées par FCM. Les clics sur des notifications que vous avez vous-même affichées avec Notifee, comme celles en premier plan mentionnées ci-dessus, arrivent via les API d’événements de Notifee (onForegroundEvent, onBackgroundEvent et getInitialNotification). Ne gérer qu’un seul type de notification est une cause fréquente d’erreurs du type « le clic n’a aucun effet ».

Chargements utiles conçus pour la navigation

Une notification qui ne dit que

New Order

n’indique pas à l’application où l’utilisateur doit se rendre. Ajoutez des données structurées à côté du texte visible :

{
  "notification": {
    "title": "New Order",
    "body": "Order #12345 has been confirmed"
  },
  "data": {
    "type": "order",
    "orderId": "12345"
  }
}

Une fonction de routage peut alors associer le type à une page :

function handleNotification(data: any) {
  if (data.type === 'order') {
    navigation.navigate(
      'OrderDetails',
      {
        orderId: data.orderId,
      },
    );
  }
}

Puisque les gestionnaires de notifications s’exécutent souvent en dehors des composants, la navigation dans de tels outils correspond généralement à une référence de navigation créée au niveau racine de l’application. Pour en savoir plus sur le routage via des liens profonds, consultez le routage des notifications push via des liens profonds dans React Native.

Détecter les changements de permissions après l’installation

Les permissions ne sont pas stables dans le temps. Un utilisateur peut autoriser les notifications un lundi :

Monday:
Notifications = ON

et les désactiver depuis les paramètres du système un mercredi :

Wednesday:
User opens Android SettingsNotifications = OFF

L’application doit s’en apercevoir. Notifee lit les paramètres actuels sans demander d’autorisation :

import notifee, {
  AuthorizationStatus,
} from '@notifee/react-native';
export async function checkNotificationPermission() {
  const settings =
    await notifee.getNotificationSettings();  if (
    settings.authorizationStatus ===
    AuthorizationStatus.AUTHORIZED
  ) {
    return true;
  }  return false;
}

Notez que cette vérification renvoie false pour une autorisation provisoire ; incluez le paramètre PROVISIONAL si votre application la considère comme activée. Réexécutez-la lorsque l’application revient en premier plan.

Lorsque l’utilisateur refuse

N’affichez pas à nouveau la demande. Montrez plutôt un état clair, comme une bannière :

Notifications are disabled

avec une action menant aux paramètres de configuration :

Enable notifications from Settings
to receive important updates.[Open Settings]

Notifee propose des API qui ouvrent directement les paramètres de notification de l’application. Les principes sous-jacents à tout cela peuvent être résumés en une seule carte :

Don't force
Don't spam
Explain the value
Give the user control

Centralisation de la configuration des notifications

Dans le code en production, conservez tout ce qui concerne les notifications dans quelques modules bien définis :

src/
│
├── services/
│   ├── notificationService.ts
│   └── firebaseMessaging.ts
│
├── navigation/
│   └── notificationNavigation.ts
│
├── screens/
│
└── App.tsx

Le service principal :

// notificationService.ts

Il expose un initialiseur qui demande l’autorisation de l’utilisateur, s’arrête si celui-ci refuse, et crée sinon le canal par défaut :

import notifee, {
  AndroidImportance,
  AuthorizationStatus,
} from '@notifee/react-native';export async function initializeNotifications() {
  const settings =
    await notifee.requestPermission();  const authorized =
    settings.authorizationStatus ===
      AuthorizationStatus.AUTHORIZED ||
    settings.authorizationStatus ===
      AuthorizationStatus.PROVISIONAL;  if (!authorized) {
    return false;
  }  await notifee.createChannel({
    id: 'default',
    name: 'Default Notifications',
    importance: AndroidImportance.DEFAULT,
  });  return true;
}

L’appel depuis un composant est simple :

useEffect(() => {
  initializeNotifications();
}, []);

Faites attention : sur la première page, cela affiche immédiatement une demande, ce qui pose le problème de timing mentionné précédemment. Appelons-le plutôt depuis le bouton d’acceptation.

Le côté backend

users
│
├── id
├── name
└── email
user_devices
│
├── id
├── user_id
├── device_id
├── fcm_token
├── platform
├── created_at
└── updated_at

Un enregistrement de registration d’appareil peut ressembler à ceci :

{
  "userId": 101,
  "deviceId": "device-abc",
  "fcmToken": "xxxxx",
  "platform": "android"
}

Lorsqu’un événement métier se produit, le backend recherche les tokens de l’utilisateur et les envoie via FCM :

Order Created
      │
      ▼
Backend
      │
      ▼
Find user's FCM token
      │
      ▼
Firebase Cloud Messaging
      │
      ▼
User's phone

FCM attend que les messages soient envoyés depuis un environnement fiable, comme votre serveur d’application ou Cloud Functions, et jamais depuis le client, car l’envoi nécessite des identifiants de serveur.

Chargements utiles évolutifs

Concevez chaque chargement utile de manière à ce que l’application comprenne l’événement, et non seulement le texte. Une confirmation de paiement pourrait contenir :

{
  "notification": {
    "title": "Payment Successful",
    "body": "Your payment of ₹999 was successful."
  },
  "data": {
    "type": "payment",
    "paymentId": "PAY12345",
    "screen": "PaymentDetails"
  }
}

Un seul switch basé sur data.type gère alors tous les types de notifications :

switch (data.type) {
  case 'payment':
    navigation.navigate(
      'PaymentDetails',
      {
        paymentId: data.paymentId,
      },
    );
    break;
  case 'order':
    navigation.navigate(
      'OrderDetails',
      {
        orderId: data.orderId,
      },
    );
    break;
}

Chaque nouveau type de notification correspond à un nouvel état dans le switch. Les valeurs des données FCM sont des chaînes de caractères, il faut donc parser les nombres explicitement.

L’architecture complète

En assemblant ces éléments, une configuration en production ressemble à ceci :

┌──────────────────┐
                    │    Backend API   │
                    └────────┬─────────┘
                             │
                             ▼
                    ┌──────────────────┐
                    │ Firebase / FCM   │
                    └────────┬─────────┘
                             │
                ┌────────────┴────────────┐
                │                         │
                ▼                         ▼
        ┌──────────────┐          ┌──────────────┐
        │    Android   │          │     iOS      │
        └──────┬───────┘          └──────┬───────┘
               │                         │
               ▼                         ▼
        Notification                 APNs / FCM
          Channel                        │
               │                         │
               └────────────┬────────────┘
                            ▼
                    React Native App
                            │
                            ▼
                    Navigation / UI

Sur iOS, FCM ne livre pas les messages directement ; il les transmet via le service Apple Push Notification (APNs), c’est pourquoi les identifiants d’APNs doivent être configurés.

Erreurs courantes

Demande d’autorisation au premier lancement

Cette approche sur l’écran d’accueil, sans aucune explication, constitue l’erreur la plus fréquente :

useEffect(() => {
  requestNotificationPermission();
}, []);

Expliquez d’abord la valeur et demandez l’autorisation en réponse à une action de l’utilisateur.

Oublier les canaux

Sans canal, les notifications ne s’afficheront pas sur Android 8.0 et versions ultérieures. Créez-en un avant de les afficher :

await notifee.createChannel({
  id: 'messages',
  name: 'Messages',
});

Esperer l’apparition de notifications en premier plan

L’écoute via

messaging().onMessage(...)

n’implique pas nécessairement l’apparition d’une notification système visible dans tous les cas. Pour une présentation cohérente en premier plan, affichez-la vous-même à l’aide de Notifee.

Ignorer les changements de token

Un token enregistré une fois cesse silencieusement de fonctionner. Utilisez

messaging().onTokenRefresh(...)

et envoyez chaque nouveau token au serveur.

Enregistrer des gestionnaires en arrière-plan dans les composants

Le gestionnaire en arrière-plan doit se trouver au point d’entrée, en dehors de l’arbre React, comme indiqué précédemment.

Ignorer les modifications ultérieures des permissions

Les utilisateurs peuvent révoquer des permissions ou bloquer un canal spécifique à tout moment. Vos vérifications doivent prendre en compte chacun de ces états :

Granted
Denied
Changed externally
Channel blocked
App notifications disabled

Fuite de données sensibles

Le texte de notification est visible sur l’écran verrouillé ainsi que pour toute personne se trouvant à proximité de l’appareil. Plutôt que

Your bank account ending in 1234 received ₹50,000.

de envoyer quelque chose de neutre :

New account activity
Tap to view details.

et d’afficher les détails uniquement après que l’utilisateur s’est authentifié dans l’application.

Liste de contrôle pour les tests

Le comportement varie en fonction de la version du système d’exploitation, de l’état de l’application et du dispositif, il convient donc de tester de manière systématique. Sur Android :

☐ Android 12
☐ Android 13+
☐ Permission granted
☐ Permission denied
☐ Permission disabled from Settings
☐ Channel enabled
☐ Channel disabled
☐ App foreground
☐ App background
☐ App killed
☐ Notification tapped
☐ Multiple notifications

Sur iOS :

☐ Permission granted
☐ Permission denied
☐ Permission changed from Settings
☐ App foreground
☐ App background
☐ App terminated
☐ Notification tapped
☐ Badge
☐ Sound
☐ Deep linking

Et pour l’intégration FCM de bout en bout :

☐ Token generated
☐ Token stored on backend
☐ Token refresh handled
☐ Foreground message received
☐ Background message received
☐ Notification click handled
☐ Initial notification handled

Flux à l’exécution en bref

La séquence complète, de la mise en route de l’application jusqu’à la livraison et à la navigation, est la suivante :

APP START
                       │
                       ▼
              Check permissions
                       │
              ┌────────┴────────┐
              │                 │
           Allowed            Denied
              │                 │
              ▼                 ▼
       Initialize FCM      Show explanation
              │                 │
              ▼                 ▼
       Get FCM token       Request permission
              │
              ▼
       Send token to API
              │
              ▼
          App Running
              │
       ┌──────┴──────┐
       │             │
       ▼             ▼
   Foreground     Background
       │             │
       ▼             ▼
    onMessage      FCM/APNs
       │             │
       ▼             ▼
    Notifee       System UI
       │             │
       └──────┬──────┘
              ▼
        User taps
              │
              ▼
       Read notification data
              │
              ▼
          Navigation

Notifications locales contre notifications push, et utilisation des deux ensemble

Ces deux types de notifications diffèrent sur quelques points prévisibles :

  • Déclencheur : les notifications locales proviennent de l’application ou du dispositif ; les notifications push proviennent de votre backend.
  • Réseau : les notifications locales fonctionnent généralement hors ligne ; les notifications push nécessitent une connexion.
  • FCM : il n’est pas nécessaire pour les notifications locales ; il est généralement requis pour les notifications push.
  • Rappels planifiés : c’est l’usage naturel des notifications locales ; ce n’est pas l’usage typique des notifications push.
  • Événements serveur, mises à jour de commande, messages de chat : les notifications push sont le choix normal ; les notifications locales ne peuvent les gérer que de manière limitée.
  • Bibliothèques : Notifee pour les notifications locales ; FCM associé à une bibliothèque d’affichage pour les notifications push.

Ils s’associent également bien. Une configuration courante permet à FCM de livrer le message, à votre code de le traiter, et à Notifee de le présenter :

FCM receives message
        ↓
React Native processes message
        ↓
Notifee displays native notification

Ce que les utilisateurs doivent voir à chaque étape

Pour une vérification manuelle, sachez à quoi vous attendre. Sur Android 13+, une boîte de dialogue système apparaît :

Allow MyApp to send you notifications?

Dans les paramètres de notification système de l’application, les canaux que vous avez créés apparaissent sous forme d’interrupteurs séparés :

MyApp
 ├── Messages
 ├── Orders
 └── Promotions

Sur iOS, une demande d’autorisation système apparaît, et les notifications envoyées se trouvent dans le Centre de notifications. Pour les notifications push, la console Firebase peut envoyer un message de test directement vers un token d’appareil, ce qui est le moyen le plus rapide pour distinguer les bugs du côté serveur de ceux du côté client :

Firebase Console
      ↓
Cloud Messaging
      ↓
Test Message
      ↓
FCM Token

Finalement, en tapant sur une notification de commande :

Notification
     ↓
Order #12345

on doit arriver sur l’écran correspondant :

Order Details Screen

Bonnes pratiques en environnement de production

Planifiez la demande de permission de manière à ce que les utilisateurs comprennent pourquoi les notifications sont utiles, plutôt que de les demander simplement parce que l’application a démarré. Gardez la logique centralisée dans un seul service au lieu de la dupliquer sur plusieurs écrans :

notificationService.ts

Séparez les types de notifications en canaux ou catégories telles que :

messages
orders
payments
security
marketing

Concevez les en-têtes de données pour la navigation, y incluant les champs appropriés :

type
id
screen

En outre :

  • Liez les tokens aux utilisateurs et appareils authentifiés, stockez-les de manière sécurisée et gérez leur renouvellement.
  • Évitez d’inclure des informations sensibles dans le texte des notifications, car celui-ci est visible en dehors de l’application.
  • Testez sur des appareils réels, car les émulateurs et simulateurs peuvent se comporter différemment.

En résumé

Un système de notifications fonctionnel va bien au-delà d’une simple appel comme

showNotification();

C’est la combinaison de tous ces éléments qui compte :

Permissions
     +
Notification Channels
     +
FCM Tokens
     +
Foreground Handling
     +
Background Handling
     +
Notification Clicks
     +
Deep Linking
     +
Backend Integration

Le principe fondamental est une répartition claire des responsabilités. FCM s’occupe de la livraison des messages. Le système d’exploitation gère les permissions ainsi que l’affichage natif. Notifee permet à React Native de contrôler l’affichage local. Votre backend décide quand envoyer quoi et à qui. Considéré comme un seul système, il fonctionne de la manière suivante :

┌─────────────────┐
              │   Backend API   │
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │      FCM        │
              └────────┬────────┘
                       │
              ┌────────┴────────┐
              ▼                 ▼
        ┌───────────┐     ┌───────────┐
        │  Android  │     │    iOS    │
        │  Channel  │     │   APNs    │
        └─────┬─────┘     └─────┬─────┘
              │                 │
              └────────┬────────┘
                       ▼
                React Native
                       │
                       ▼
                 Notifee
                       │
                       ▼
                Native UI

Avec ces limites bien définies, les notifications se comportent de manière prévisible sur Android et iOS, ainsi que dans les états en premier plan, en arrière-plan ou terminé.

Référence rapide

Demander la permission Android :

await PermissionsAndroid.request(
  PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
);

Demander la permission iOS :

await notifee.requestPermission();

Créer un canal Android :

await notifee.createChannel({
  id: 'messages',
  name: 'Messages',
});

Afficher une notification :

await notifee.displayNotification({
  title: 'New Message',
  body: 'You have a new message.',
  android: {
    channelId: 'messages',
  },
});

Écouter les messages FCM en premier plan :

messaging().onMessage(async message => {
  console.log(message);
});

Écouter les changements de token :

messaging().onTokenRefresh(token => {
  console.log(token);
});

Gérer l’ouverture des notifications depuis l’arrière-plan :

messaging().onNotificationOpenedApp(message => {
  console.log(message.data);
});