Notifications push bien faites : le guide complet pour Flutter et Firebase (iOS et Android)
Une application qui ne parle jamais à ses utilisateurs finit oubliée au fond d’un écran d’accueil. Les notifications push sont le canal le plus direct pour revenir vers quelqu’un qui n’a pas ouvert l’app depuis trois jours, sans email, sans SMS et sans passer par un tiers. Encore faut-il qu’elles arrivent vraiment, dans tous les cas : app ouverte, app en arrière-plan, app fermée.
C’est là que ça se complique. Sur iOS, une notification reçue pendant que l’app est au premier plan ne s’affiche pas toute seule. Il faut une clé APNs, deux capacités Xcode, un fichier Swift à retoucher et deux services Dart qui se passent le relais. Ce guide déroule toute la chaîne avec Flutter et Firebase Cloud Messaging, du projet Firebase vide jusqu’au test réel sur un iPhone, écran par écran.
Il s’adresse aux développeurs Flutter qui n’ont jamais branché de push, et aux porteurs de projet qui veulent comprendre ce que leur prestataire va facturer sous la ligne « notifications ». À la fin, vous saurez pourquoi il faut deux plugins et non un seul, et ce que chaque réglage change concrètement sur le téléphone.
Les points essentiels à retenir
- Trois plugins suffisent :
firebase_core,firebase_messagingetflutter_local_notifications, ce dernier uniquement pour l’affichage quand l’app est au premier plan. - Côté Apple, tout repose sur une clé APNs (.p8) créée dans le compte développeur puis importée dans Firebase avec son Key ID et le Team ID.
- Dans Xcode, la cible Runner doit recevoir deux capacités : Push Notifications et Background Modes avec Remote notifications cochée.
- Le plugin Firebase affiche seul les notifications quand l’app est fermée ; au premier plan, c’est le plugin local qui prend le relais via
onMessage. - Le test se fait sans backend : la console Firebase envoie un message de test au jeton FCM copié depuis les logs.
Étape 1 : créer le projet Firebase et déclarer les trois plugins
On commence par la console Firebase. Créez un nouveau projet et donnez-lui un nom parlant, par exemple « Push notifications sample ». Deux options sont proposées à la création : l’assistant Gemini et Google Analytics. Ni l’un ni l’autre n’est nécessaire pour envoyer des push, vous pouvez les désactiver tous les deux. La console le rappellera d’ailleurs plus tard avec un bandeau jaune : sans Analytics, la plupart des options de ciblage des campagnes ne sont pas disponibles, mais le message de test passe très bien.
Ouvrez ensuite votre projet Flutter et ajoutez les dépendances depuis le terminal. Trois paquets entrent en jeu, chacun avec un rôle distinct :
firebase_core: l’initialisation de Firebase dans l’app, obligatoire pour tout le reste.firebase_messaging: la réception des messages distants envoyés par Firebase Cloud Messaging.flutter_local_notifications: l’affichage des notifications quand l’app est ouverte, ce que le plugin Firebase ne fait pas tout seul.

Pourquoi ce troisième paquet ? Parce que le comportement par défaut de Firebase Messaging dépend de l’état de l’app. App fermée ou en arrière-plan : la notification s’affiche dans le centre de notifications sans rien coder. App au premier plan : le message arrive bien dans votre code Dart, mais rien n’apparaît à l’écran. Le plugin local sert précisément à combler ce trou.
Étape 2 : relier l’app au projet avec FlutterFire CLI
Configurer Firebase à la main, c’est télécharger un fichier de configuration par plateforme et le déposer au bon endroit dans le projet natif. La ligne de commande FlutterFire fait ça en une fois. Trois commandes suffisent :
firebase loginpour vérifier que vous êtes bien authentifié sur le compte qui possède le projet.- Activer la CLI FlutterFire (voir la documentation officielle si elle n’est pas encore installée sur votre machine).
flutterfire configure, lancé à la racine du projet Flutter.
Le terminal liste alors vos projets Firebase : sélectionnez celui créé à l’étape 1. Il demande ensuite les plateformes à configurer. Cochez Android et iOS, décochez le reste si vous n’en avez pas besoin.

À la fin, la CLI a enregistré une application Android et une application iOS dans la console Firebase, récupéré GoogleService-Info.plist pour le module iOS et google-services.json pour Android, et généré un fichier Dart avec la classe DefaultFirebaseOptions. C’est cette classe qu’on réutilisera dans main.dart. Pour Android, c’est quasiment tout : le reste de ce guide concerne surtout iOS, qui exige une chaîne de confiance avec Apple.
Étape 3 : créer la clé APNs dans le compte développeur Apple
Apple ne laisse pas n’importe quel serveur pousser des notifications vers un iPhone. Firebase doit prouver qu’il agit pour votre compte, et cette preuve est une clé APNs (Apple Push Notification service). Si vous n’avez pas encore de compte développeur, notre guide pour publier une application sur l’App Store explique par où commencer.
- Connectez-vous au compte développeur Apple et ouvrez la section Account, puis Keys dans Certificates, Identifiers & Profiles.
- Créez une nouvelle clé, donnez-lui un nom et cochez Apple Push Notifications service (APNs).
- Cliquez sur Configure. Pour un projet de démonstration, une seule clé pour les environnements de développement et de production suffit. Pour une app en production, Apple recommande une clé distincte par environnement.
- Terminez par Register et téléchargez le fichier .p8. Notez bien où vous le rangez : il ne pourra pas être retéléchargé.

Étape 4 : importer la clé dans Firebase avec Key ID et Team ID
Retour dans la console Firebase, cette fois dans les paramètres du projet. L’onglet Cloud Messaging contient une section Apple app configuration, avec l’application iOS créée par la CLI à l’étape 2. Cliquez sur Upload dans la ligne APNs Authentication Key.
La fenêtre demande trois choses :
- le fichier .p8 téléchargé à l’étape précédente ;
- le Key ID, affiché sur la page de la clé côté Apple ;
- le Team ID, l’identifiant de votre équipe, visible dans l’en-tête du compte développeur.

Une fois l’import validé, Firebase peut signer ses requêtes vers APNs pour votre application. Sans cette étape, tout le code Dart du monde n’affichera rien sur iPhone.
Étape 5 : activer Push Notifications et Background Modes dans Xcode
Le projet iOS doit lui aussi déclarer qu’il accepte des notifications distantes. Depuis Android Studio, sélectionnez le dossier ios, puis passez par le menu Tools, Flutter, Open iOS module in Xcode. Dans Xcode :
- Sélectionnez le projet Runner, puis la cible Runner.
- Ouvrez l’onglet Signing & Capabilities et cliquez sur + Capability.
- Cherchez Push Notifications et ajoutez-la.
- Recommencez avec Background Modes. Dans la liste d’options qui apparaît, cochez Remote notifications.

La capacité Background Modes est facile à oublier. Sans Remote notifications, l’app ne peut pas réagir à un push quand elle n’est pas active au premier plan, et le gestionnaire d’arrière-plan qu’on écrira à l’étape 7 ne sera jamais appelé.
Étape 6 : écrire le service de notifications locales
Place au code Dart. Créez un dossier services dans lib et, dedans, un fichier pour le service de notifications locales. Comme une seule instance du plugin doit exister dans l’app, le service est un singleton : constructeur privé, instance statique finale, et un constructeur factory qui renvoie toujours cette instance.
Le cœur du service est une méthode showNotification qui reçoit un titre, un corps et un payload. Elle assemble les détails propres à chaque plateforme avant d’appeler le plugin :
- pour Android, un
AndroidNotificationDetailsavec l’identifiant, le nom et la description du canal, une importance maximale et une priorité haute ; - pour iOS, un
DarwinNotificationDetails; - les deux sont réunis dans un
NotificationDetails, puis passés àshow()avec un compteur d’identifiants incrémenté à chaque appel.

Ce service ne sait rien de Firebase. Il affiche ce qu’on lui donne, point. C’est volontaire : le jour où vous voudrez déclencher une notification depuis une autre source (un rappel local, un événement métier), il servira tel quel.
Étape 7 : retoucher AppDelegate.swift pour iOS
Le plugin local a besoin d’un petit coup de pouce côté natif. Toujours dans Xcode, ouvrez le dossier Runner puis le fichier AppDelegate.swift. Ajoutez l’import flutter_local_notifications en haut, puis deux blocs dans didFinishLaunchingWithOptions :
- un appel à
FlutterLocalNotificationsPlugin.setPluginRegistrantCallback, qui enregistre les plugins générés auprès du registre : c’est ce qui connecte le plugin de notifications au système iOS ; - sous une condition
#available(iOS 10.0, *), l’affectation deUNUserNotificationCenter.current().delegateàself, pour qu’iOS renvoie à Flutter les événements comme le tap sur une notification.
Combien coûterait votre application ?
Un devis estimatif détaillé en six questions, à partir des fourchettes de notre guide des prix.

Le premier bloc est encadré en rouge sur la capture ; la page du plugin sur pub.dev détaille les deux. Le second est ce qui permettra plus tard de router un tap vers le bon écran de l’app.
Étape 8 : le service Firebase Messaging, premier plan et arrière-plan
Second fichier dans services : le service Firebase Messaging, singleton lui aussi. Sa méthode init reçoit l’instance du service local et enchaîne quatre actions :
- mémoriser le service local, qui servira à afficher les messages reçus au premier plan ;
- gérer le jeton FCM :
getToken()pour le récupérer et l’écrire dans les logs, puisonTokenRefreshpour être prévenu quand il change. Le code laisse un TODO explicite : envoyer ce jeton à votre serveur pour cibler l’appareil ; - demander la permission à l’utilisateur avec
requestPermission, alertes, badge et son activés ; - enregistrer deux gestionnaires :
FirebaseMessaging.onBackgroundMessagepour les messages reçus app fermée, etFirebaseMessaging.onMessage.listenpour ceux reçus au premier plan.

C’est dans le gestionnaire de premier plan que la boucle se ferme : quand un message arrive alors que l’app est ouverte, on prend son titre et son corps et on les passe au showNotification du service local. Le gestionnaire d’arrière-plan, lui, n’a pas besoin d’afficher quoi que ce soit, le plugin Firebase s’en charge.
Étape 9 : brancher les deux services dans main.dart
Il reste à tout initialiser au démarrage. Dans la fonction main, passée en async :
WidgetsFlutterBinding.ensureInitialized(), sinon Firebase refuse de démarrer avant que le binding Flutter soit prêt ;Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform), la classe générée par FlutterFire à l’étape 2 ;- récupérer
LocalNotificationsService.instanceet appeler soninit(); - récupérer
FirebaseMessagingService.instanceet appelerinit()en lui passant le service local ; - enfin
runApp.

L’ordre n’est pas décoratif. Le service Messaging dépend du service local ; le service local dépend du plugin natif enregistré à l’étape 7 ; et les deux dépendent d’un Firebase déjà initialisé.
Étape 10 : tester depuis la console Firebase, app ouverte puis fermée
Lancez l’app sur un iPhone. Au premier démarrage, iOS affiche la demande d’autorisation générée par requestPermission. Acceptez-la.

Juste après, le jeton FCM apparaît dans la console de l’IDE, précédé de « Push notifications token ». Copiez-le en entier. Puis, dans la console Firebase :
- Menu Run, puis Messaging.
- Create your first campaign, choisissez Firebase Notification messages et validez.
- Saisissez un titre et un texte de notification.
- Cliquez sur Send test message, collez le jeton FCM dans la fenêtre Test on device, puis Test.

Premier cas validé : l’app était ouverte, et c’est le plugin local qui a dessiné la bannière. Fermez maintenant complètement l’app, modifiez légèrement le titre pour distinguer les deux envois, et renvoyez un message de test.

Second cas validé. Les deux chemins fonctionnent, et le même code tourne sur Android sans modification notable, puisque FlutterFire a déjà déposé le fichier de configuration au bon endroit. Si vous hésitez encore entre les deux frameworks multiplateformes pour ce genre de sujet, notre comparatif Flutter ou React Native détaille ce que chacun impose côté natif.
Ce que montrent les écrans, fonction par fonction
Une chaîne technique qui fonctionne ne fait pas encore une notification « bien faite ». Ce qui compte, c’est ce que le message déclenche une fois l’app ouverte. Deux applications servent de repère ici, parce que leurs écrans illustrent bien le modèle déclencheur, action, récompense variable, investissement (Hooked, 2014). Les captures sont celles des fiches App Store France.
Duolingo : le catalogue de cours, premier écran après le déclencheur

Ce qu’on voit : un panneau « Cours » avec une liste de langues et de matières, chacune précédée d’un drapeau ou d’une icône, et un chevron à droite. Deux sections, « Pour les personnes parlant français » puis « For English speakers ». Une croix en haut à gauche pour refermer.
Le geste : un tap sur une ligne. Rien d’autre à saisir, pas de formulaire.
Le lien avec l’écran suivant : c’est l’action minimale du modèle. Quand une notification de rappel amène quelqu’un jusqu’ici, la friction est réduite à un choix parmi une liste. Le déclencheur externe (la notification) a fait son travail ; l’écran ne doit pas le gâcher en demandant un effort. Le tap ouvre directement une leçon, donc la récompense.
Duolingo : l’exercice et son « Bien joué ! », la récompense variable

Ce qu’on voit : une question, « Comment dit-on verre ? », quatre cartes illustrées, la bonne réponse encadrée en bleu, un bandeau vert « Bien joué ! » avec un bouton Continuer, et en haut une barre de progression et un compteur de cœurs (5).
Le geste : taper une carte, puis Continuer. Deux taps, une boucle courte.
Le lien avec l’écran suivant : la récompense n’est pas toujours la même (bonne ou mauvaise réponse, cœur perdu ou non, progression qui avance), c’est ce qui la rend variable. Et la barre de progression est l’investissement : chaque leçon terminée donne une raison de revenir demain, ce qui rend la prochaine notification de rappel légitime plutôt qu’intrusive. La fiche complète de l’app est sur notre page modèle Duolingo.
Instagram : la story, un contenu à réponse immédiate

Ce qu’on voit : l’éditeur de story avec une photo, un texte en surimpression, une mention de compte, un sticker de lieu et un sticker interactif « Présentez-nous votre meilleur(e) ami(e) » avec un bouton « Ajout perso ». En bas, deux destinations : Votre story et Ami(e)s proches.
Le geste : composer, puis choisir l’audience et publier.
Le lien avec l’écran suivant : publier une story est un investissement, et un sticker interactif appelle des réponses. Chaque réponse reçue est un déclencheur naturel pour une notification, et cette notification a une chance d’être bienvenue parce que l’utilisateur a lui-même provoqué l’échange. C’est la différence entre un push qui informe d’une action d’autrui et un push promotionnel.
Instagram : la messagerie, là où la notification atterrit

Ce qu’on voit : une conversation avec, en haut, une story partagée et ses réactions emoji, puis des bulles de messages, une ligne « Vu par Ana, Alex Walker et 3 autres personnes » et un champ de saisie avec micro, image et sticker.
Le geste : lire, réagir d’un tap, répondre.
Le lien avec l’écran précédent : c’est ici que le tap sur une notification doit arriver, directement dans le bon fil. Techniquement, c’est le rôle du payload passé à showNotification à l’étape 6 et du delegate configuré dans AppDelegate à l’étape 7 : sans eux, le tap ouvre l’app sur son écran d’accueil et l’utilisateur doit chercher. Le détail des écrans est sur notre page modèle Instagram.
Les erreurs fréquentes
- Croire que Firebase Messaging affiche tout. Il affiche les notifications quand l’app est fermée. Au premier plan, sans le plugin local branché sur
onMessage, le message est reçu mais invisible. - Oublier Background Modes. Ajouter Push Notifications ne suffit pas ; il faut aussi Background Modes avec Remote notifications, sinon rien ne se passe app en arrière-plan.
- Réutiliser une seule clé APNs en production. Acceptable pour un projet de démonstration, mais Apple recommande une clé par environnement, développement et production.
- Laisser le jeton dans les logs. Le code de démonstration l’imprime pour le test. En production, il doit être envoyé à votre serveur à chaque
onTokenRefresh, sinon vous ne pourrez cibler aucun appareil.
Ces points ont un coût, et il vaut mieux qu’il soit visible dès le devis. Si vous préparez un projet, prévoyez une ligne dédiée dans votre cahier des charges d’application mobile et vérifiez qu’elle apparaît dans le devis : configuration APNs, gestion du jeton côté serveur, et routage du tap vers le bon écran.
Questions fréquentes
Faut-il forcément flutter_local_notifications en plus de firebase_messaging ?
Pour afficher une notification pendant que l’app est ouverte, oui. Firebase Messaging seul ne dessine rien au premier plan. En revanche, si vous voulez un contrôle total, le plugin local peut aussi prendre en charge l’affichage quand l’app est fermée, en remplaçant le comportement par défaut. C’est une configuration plus avancée, hors du périmètre de ce guide.
Où trouver le Key ID et le Team ID demandés par Firebase ?
Les deux sont dans le compte développeur Apple. Le Key ID est affiché sur la page de la clé APNs après sa création ; le Team ID figure dans l’en-tête du compte, à côté du nom de l’équipe.
Le code fonctionne-t-il tel quel sur Android ?
Oui. Les services Dart sont communs aux deux plateformes, et FlutterFire CLI a déposé google-services.json à la bonne place. Les étapes propres à iOS (clé APNs, capacités Xcode, AppDelegate) n’ont pas d’équivalent à faire à la main côté Android.
Comment envoyer une notification sans passer par la console Firebase ?
La console sert au test. En production, c’est votre serveur qui cible les appareils à partir des jetons collectés depuis l’app, d’où le TODO laissé dans le code : remonter le jeton à chaque onTokenRefresh. Le vocabulaire complet est repris dans notre définition de la notification push.
Quiz : avez-vous tout retenu ?
Quel plugin affiche la notification quand l’app Flutter est au premier plan ?
- firebase_core
- firebase_messaging
- flutter_local_notifications
- flutterfire_cli
Firebase Messaging reçoit le message mais ne l’affiche pas au premier plan ; c’est le plugin local, appelé depuis onMessage, qui dessine la bannière.
Quelle capacité Xcode doit accompagner Push Notifications pour recevoir des push en arrière-plan ?
- App Groups
- Background Modes avec Remote notifications
- Sign in with Apple
- Associated Domains
Sans Remote notifications coché dans Background Modes, l’app ne réagit pas aux push quand elle n’est pas active.
Que faut-il fournir à Firebase en plus du fichier .p8 ?
- Le Bundle ID et le mot de passe Apple
- Le Key ID et le Team ID
- Le certificat de distribution
- Le jeton FCM
La fenêtre Upload APNs auth key réclame le Key ID de la clé et le Team ID du compte développeur, tous deux marqués « required ».
Dans main.dart, que faut-il appeler avant Firebase.initializeApp ?
- runApp
- FirebaseMessaging.instance.getToken
- WidgetsFlutterBinding.ensureInitialized
- requestPermission
Le binding Flutter doit être prêt avant toute initialisation de plugin natif, Firebase compris.
Besoin d’un avis technique sur votre projet d’application ?
Digital Unicorn, agence d’applications mobiles présentée dans ce guide, propose un échange gratuit de trente minutes pour situer votre projet : périmètre, technologies, ordre de grandeur budgétaire.
Réserver un créneau avec Digital Unicorn
Ou directement : contact@digitalunicorn.fr · 06 32 64 24 80. L’agence répond en général dans les 30 minutes (jours ouvrés).
Cadre du projet, ensuite
Un tutoriel store ou technique ne remplace pas un cahier des charges. Quand le périmètre existe (deux stores, un back-office, un paiement), ouvrez le modèle de CDC, les fourchettes du guide des prix, et la grille de devis. Les captures annotées de nos articles suivent la même logique que l’analyse d’écrans du CDC : une source réelle, une exigence écrite, pas une moodboard.
Fourchettes HT, France : MVP 15 000 à 40 000 €, ambition moyenne 40 000 à 120 000 €, complexe 120 000 à 300 000 €. Maintenance 15 à 20 % par an.
Par la rédaction d’Agence Application Mobile. Article publié le 16 septembre 2026.