Firebase

Présentation du cours
Présentation personnelle
- ancien de l'ESGI : Architecture des Logiciels
- spécialités : développement Flutter et Android natif
- anciennes expériences : Agence de dev mobile
- maintenant :
- Founder @ Flappy
- formateur : Flutter, Android, Git, Algo, Firebase
- organisateur du Meetup Flutter Paris
- adresse mail : thomasecalle+prof@gmail.com
Présentation des règles
- les retards
- l'attention en cours
- les supports de cours
- la notation
- un cas pratique
- un projet à faire en équipe
- je prends vos feedbacks !
Prérequis
- connaissances en développement d'application mobiles (notions de mise en production, "flavors/scheme", etc.)
- connaissances en Flutter
Petit point sur le choix de Flutter comme technologie
Jusqu'où irons-nous
- cours d'initiation de 15h = non experts
- nous allons quand même pouvoir voir de nombreux outils Firebase (mais pas tous !) :
- découverte de l'outil en général
- Analytics
- Bases de données temps réel (Realtime et Firestore)
- Firebase Authentication
- Crashlytics
- Cloud Messenging
- Cloud Functions
Firebase
Qu'est-ce que c'est ?
Mini historique
- lancement en 2011 sous le nom d'Envolve
- rachat par Google en Octobre 2014
Voilà... je vous ai pas menti sur le côté "mini" de l'historique.
Une boîte à outils Cloud
Vous pouvez voir Firebase comme une grosse boîte à outils cloud.
Créer un projet Firebase, c'est avoir accès à de nombreux outils cloud utilisables sur n'importe quel type d'application (mobile ou non, d'ailleurs).
Une boîte à outils Cloud
-
Build
outils de management de backend, de machine learning, d'hébergement, etc.
-
Release & Monitoring
Store d'applications, outils de testing, outils d'analyse de crashs, etc.
-
Engage
Envoie de notifications ciblées, A/B testing, mise en place de segments utilisateurs, etc.
Pourquoi l'apprendre ?
Firebase est devenu indispensable dans la vie d'un développeur d'applications mobiles !
Il y a fort à parier que vous ayez déjà eu affaire à au moins l'un de ses outils pour une application mobile
Comprendre son fonctionnement, l'utilité des différents outils, leur utilisation / pertinence, leur implication sur un vrai projet, etc. est essentiel
Vraiment indispensable ?
Oui ... et non.
Un développeur mobile se doit de comprendre encore une fois ses rouages et de savoir utiliser ses outils à bon escient
MAIS, la sur-utilisation de Firebase peut également être un problème sur le long terme !
(anecdote personnelle sur une appli avec sur-utilisation de Firebase)
Vraiment indispensable ?
Il faut savoir utiliser Firebase avec pertinence. Parfois nous en avons besoin, parfois non. Comprendre la pertinence des outils et savoir quand les utiliser ou non peut faire une vraie différence !
C'est l'objectif de ce cours :)
Créer un projet Firebase
Projet Firebase
Pour commencer à jouer avec Firebase, RDV sur la :
Projet Firebase
Votre console Firebase est liée à votre compte Gmail.
C'est peut-être un détail lorsqu'on débute, mais faites bien attention à créer un projet lié à l'entreprise X dans la console firebase liée au compte Gmail de l'entreprise X
Projet Firebase
Par exemple, ma Console Firebase thomasecalle@gmail.com regroupe mes petits tests persos
Pour chaque projet "pro", je crée mon projet Firebase dans la Console associée
Exemple avec ma startup COMO associée au gmail como-app
Projet Firebase
Ca peut sembler être un détail, mais ça structure beaucoup votre projet et vous pourrez regrettez plus tard de ne pas avoir pensé à ça
Exemple pour une Agence :
- on crée le projet sur la console du client
- on s'ajoute en administrateur
- comme ça, si on arrête de bosser ensemble, le client a FULL accès à la console et peut nous virer
Projet Firebase
Ok, maintenant que nous avons vu ça... c'est quoi, un projet Firebase ?
Un projet Firebase représente un projet d'application
Si on reprend notre exemple de boîte à outils, c'est donc la boîte à outils associée au projet X
Firebase utilise le terme de "Conteneur" cloud pour vos applications
Projet Firebase
Dans un projet, vous allez pouvoir enregistrer à chaque fois une application iOS, une Android et une WEB
En théorie du coup, chaque application mobile différente est un nouveau projet Firebase !
Pourquoi en théorie ?
Projet Firebase
Lorsqu'on développe une application mobile sérieuse, nous mettons souvent en place des "saveurs / scheme" ("saveurs" étant la traduction pour flavors)
D'ailleurs, petit aparté, si vous ne le faites pas... eh bien faites le !
Gérer des saveurs différentes pour une appli mobile devient indispensable pour une application mobile professionnelle
Projet Firebase
Quel rapport avec un projet Firebase ?
Lorsque vous créer des saveurs pour vos apps, ça signifie souvent que vous allez gérer différents identifiants (package name côté Android & Bundle ID côté iOS)
Ca signifie que ce sont vraiment des applications différentes !
Projet Firebase
Lorsqu'on enregistre une application dans un projet Firebase, on indique l'identifiant de celle-ci, car Firebase sait que celui-ci est UNIQUE et donc s'assure qu'il pourra spécifiquement cibler celle-ci
Ainsi, vous allez souvent avoir autant de projets Firebase que de saveurs :
- un projet DEV avec les apps de dev
- un projet PROD avec les apps de prod
- etc.
Projet Firebase
Ca fait partie des petits détails qui changent tout lorsqu'on passe d'applications faites à l'arrache à des applications professionnelles
Le tout étant de bien comprendre tout ça pour ne pas prendre de mauvaises décisions
Projet Firebase
Dernier "détail", et pas des moindre, avant de créer notre premier projet :
Un projet Firebase, c'est un projet Google Cloud
Projet Firebase
En fait, lorsque vous créez un projet Firebase, ça vous crée en arrière plan un projet Google Cloud
Voyez Firebase comme une interface sexy donnant accès à tous les services Google Cloud + des configurations et outils spécifiques à Firebase
Projet Firebase
Pourquoi c'est important à comprendre ?
Parce que, du coup, c'est Google derrière !
C'est parfois un frein pour certaines entreprises et c'est votre rôle de développeur de comprendre les conséquences de tels choix et d'être capable de les expliquer
Projet Firebase
D'ailleurs, d'autres acteurs proposent des concurrents
C'est le cas surtout d'AWS Amplify, la solution proposée par Amazon
Bien que ça n'ait pour le moment pas autant d'impact que Firebase, c'est important de l'avoir à l'oeil et de s'y intéresser
Projet Firebase
Il y a également des concurrents Open Source !
Projet Firebase
Peut-être que certains de ces outils vous conviendront mieux que Firebase
Peut-être que Firebase se fera un jour remplacer par l'équivalent Amazon ou un autre
Mais, aujourd'hui, Firebase est leader sur le marché et il est important d'y prêter attention en tant que dévelopeur mobile
Projet Firebase
OK.
Maintenant, on peut créer notre premier projet Firebase :

Projet Firebase
On donne d'abord le nom de notre projet

Observez l'identifiant généré. C'est lui qui doit être unique
Projet Firebase
Ajouter (ou non) Google Analytics à notre projet

Projet Firebase
Ajouter Google Analytics n'est pas anodin
L'idée derrière étant de récupérer pas mal d'informations sur la navigation de l'utilisateur dans votre application, il faut que vous ayez conscience des répercussions que ça a par rapport à la RGPD
Cependant, c'est souvent bien utile
Projet Firebase
Vous devrez ensuite lier ce projet Firebase à un compte Google Analytics
Il est tout à fait possible de vouloir lier plusieurs projets à un même compte ou encore que votre client/projet ait déjà un compte existant que vous pourrez lier
Projet Firebase
Sinon, vous pouvez en créer un

Projet Firebase
Et voilà !

Tour d'horizon
Tour d'horizon

Sur le panneau de gauche, vous trouverez l'ensemble des services proposés par Firebase
Tour d'horizon

Sélection du projet
Lier une application (iOS, Android ou web) au projet
Tour d'horizon

Paramètre généraux du projet
Firebase, c'est payant ?
Firebase, c'est payant ?
EVIDEMMENT !
Tous ces services sont hébergés et entièrement gérés par les infrastructures de Google
Comme tous les services Cloud du genre, il va falloir payer
Firebase, c'est payant ?
MAIS
Une très grosse partie peut être entièrement gratuite
En fait, vous avez 2 plans possible :
- Spark (totalement gratuit mais limité)
- Blaze ("pay as you go", avec gratuité sur les limites du Spark)
Firebase, c'est payant ?
Beaucoup de services sont gratuits quoi qu'il arrive :
- Analytics
- A/B Testing
- App Distribution
- Crashlytics
- Cloud Messaging
- Dynamic Links
- etc.
Pour les autres services, ça dépend de votre mode :
Firebase, c'est payant ?
Spark
En mode Spark, vous êtes sûr que tout est gratuit et que vous ne serez jamais débité de quoi que ce soit
Il y a évidemment des limites d'utilisations propres à chaque service
Firebase, c'est payant ?
Spark
Par exemple, pour l'authentification par numéro de téléphone, vous serez limités à 10K d'authentification avec succès par mois
Firebase, c'est payant ?
Blaze
Le mode Blaze est défini comme "Pay as you go"
On peut difficilement faire plus clair ! En gros, vous payerez les différents services en fonction de leur utilisation !
Firebase, c'est payant ?
Blaze
En reprenant l'exemple de l'authentification par téléphone, on est à 0.06$ la vérification (en France, le prix peut-être différent pour les US, le Canada et l'Inde)
Firebase, c'est payant ?
Blaze
Attention, avec Blaze, vous gardez ce qu'offre Spark !
Par exemple, toujours pour l'authentification par téléphone, les 10K premières vérifications restent gratuites
Firebase, c'est payant ?
Alors faut-il payer ?
Comme vous pouvez le voir, les limites de Spark sont quand même très larges !
Vous pouvez à priori largement débuter sur Spark et ne passer en Blaze que lorsque vous voyez que votre application prend de l'ampleur
Firebase, c'est payant ?
Attention cependant
Le service de Cloud Functions par exemple n'est pas accessible avec Spark
Il a tout de même une grosse partie gratuite, mais son activation nécessitera quand même de passer sur Blaze
Et les Cloud Functions, si c'est mal géré... ça peut coûter cher (petite anecdote)
Firebase, c'est payant ?
Si vous voulez tester un peu tout ça, Google propose un programme de 300$ de crédits disponible sur 90 jours
Créons notre application
Créons notre application
Maintenant que le projet Firebase est prêt, on va pouvoir lier nos applications iOS & Android à celui-ci
La première étape est évidemment de créer une application mobile
Nous partirons dans ce cours sur la technologie Flutter
Pourquoi Flutter ?
- permet de voir les config sur Android ET iOS
- parce que vous avez eu normalement cours de Flutter en 3ème année MOC
- parce qu'il faut bien choisir une techno, et que Flutter c'est le 🔥
Créons notre application
Attention, pour la suite de ce cours, nous nous baserons sur la version la plus récente sur la channel Stable d SDK Flutter
Soit, à l'heure où j'écris ces lignes, la version 2.0.3
L'important étant surtout que la version soit supérieure à 2.0, cette version ayant changé pas mal de choses
Si vous n'êtes pas à jour :
flutter upgradeCréons notre application
Commençons donc par créer notre application, avec Android Studio (ou VSCode) ou directement en ligne de commande
Créons notre application
Une fois que c'est bon, nous allons pouvoir la lier à notre projet Firebase
On lie séparément l'application Android et l'application iOS car ce sont bien 2 applications complètement différentes
Créons notre application
Attention, pour mettre en place le null-safety, on va upgrade la version de Dart requise en passant de :
Créons notre application
environment:
sdk: ">=2.7.0 <3.0.0"à :
environment:
sdk: ">=2.12.0 <3.0.0"Lier l'application Android
Lier l'application Android
Commençons par Android :


Ici, seul le package Android est obligatoire. Vous le trouverez dans le build.gradle (app) de votre application
Lier l'application Android

Le certificat de signature SHA-1n'est pas obligatoire et pourra être modifié par la suite
Cependant, il est intéressant pour certains services afin que ceux-ci puissent générer une clé d'API propre à votre application
Lier l'application Android
Nous allons quand même le renseigner pour voir comment faire
Pour ce faire, rendez-vous à la racine de votre projet puis lancez la commande suivant :
cd android && ./gradlew signingReport && cd ../Lier l'application Android
Cette commande va généré le rapport de signatures de votre application Android
Il ne vous reste plus ensuite qu'à sélectionner le SHA-1 correspondant à votre mode de build d'APK

Lier l'application Android
(petit aparté sur le mode de Build)
Lorsque vous buildez votre application Android, vous avez 3 modes : debug, profile et release
En release, Flutter va utiliser le mode de compilation AOT permettant de largement améliorer les performances et va également signer votre APK avec votre keystore etc.
Lier l'application Android
En profile, Flutter va utiliser le mode de compilation AOT permettant de largement améliorer les performances mais ne va pas signer votre APK
Enfin, en debug, Flutter va compiler en JIT pour permettre le couple Hot Reload / Hot Restart et ne signera pas votre APK
Lier l'application Android
Pourquoi ces précisions ?
Parce que chaque mode de compilation génère des certificats de signature SHA-1différents !
Debug et Profile vont avoir une clé SHA-1 différente de Release, il faut donc bien prendre les 2 si on veut que les services marchent à la fois en debug et en release
Lier l'application Android
(Petit aparté sur la signature auto-gérée par le PlayStore)
Ok, on peut continuer !

Lier l'application Android

Cette étape est la plus importante !
Lier l'application Android
Sur Android comme sur iOS, Firebase va vous demander d'ajouter un fichier de configurations dans vos fichiers
Sur Android, il s'agira du : google-services.json
Sur iOS, le : GoogleService-info.plist
Lier l'application Android
Ces fichiers sont OBLIGATOIRES
En fait, voyez les comme la carte d'identité de votre application mobile par rapport au projet Firebase associé
Ces fichiers vont en effet contenir toutes les clés publiques d'API et autres informations nécessaire à Firebase pour identifier le bon projet Firebase
Lier l'application Android
Ce qui signifie que des modifications dans les settings de votre app au niveau du projet Firebase peuvent devoir entraîner une nouvelle génération du fichier (changement de package name, ajout de SHA-1, etc.)
C'est très important de comprendre ça
Lier l'application Android
De plus, vous vous rappelez de la discussion sur les flavors et la gestion de plusieurs projets Firebase ?
Il ne faudra donc pas oublier d'avoir plusieurs fichiers de configurations, un par flavor
Super important aussi ça
Lier l'application Android
On télécharge donc le fichier, et on le mets dans le dossier android/app
Si on avait plusieurs flavors, dev, preprod et prod par exemple, on mettrait les différents fichiers dans :
- android/app/src/dev
- android/app/src/preprod
- android/app/src/prod
Lier l'application Android
Ensuite, ils ne nous reste plus qu'à déclarer et appliquer le plugin google-services dans notre Gradle


Lier l'application Android
Plus tard dans votre utilisation de Firebase, il est probable que vous ayez une erreur de type
Error while merging dex archives:
The number of method references in a .dex file cannot exceed 64KLier l'application Android
C'est fini pour Android !
Vous pouvez passer les étapes suivantes sur la doc et retourner ensuite sur votre projet Firebase dans votre console
Lier l'application Android
Lier l'application iOS
Lier l'application iOS

Lier l'application iOS

Lier l'application iOS

Ici, c'est pareil que sur Android, la seule information nécessaire est le Bundle Id de votre application !
Lier l'application iOS


Lier l'application iOS
On va devoir ensuite télécharger le fichier de confs pour iOS

Lier l'application iOS
Attention, pour ajouter le fichier, et s'assurer qu'il soit ajouté au project, il va falloir le faire via XCode
En effet, l'ajouter manuellement ne suffira pas
Du coup... pour ceux qui n'ont pas de Mac... vous ferez ça une fois que vous aurez accès à un Mac ! Merci Apple !
Lier l'application iOS

Lier l'application iOS

Lier l'application iOS

Et voilà !
Attention que le nom soit bien exempt de tout (1) ou autre trucs ajoutés par votre OS si vous en aviez téléchargé plusieurs
Lier l'application iOS
On est OK pour iOS !
Vous pouvez passer la suite des indications, qui ne sont pas nécessaires pour une intégration Flutter mais le sont pour du natif
Nouveautés Flutter 3
Nouveautés Flutter 3
Le 11 mai 2022, Flutter 3 a été annoncé à la Google I/O !
Au programme :
- flutter MacOS en stable avec beaucoup de composants macOS
- écrans pliables supportés
- améliorations performances iOS
- amélioration build iOS
- amélioration gestion des images et du splashscreen en Flutter WEB
- améliorations performances Dart + enums avec paramètres
- Material 3 +
- etc.
Nouveautés Flutter 3
Quel rapport avec Firebase ?
Les relations entre Flutter et Firebase ne font que s'améliorer car Flutter devient enfin une cible de base pour Firebase :

Nouveautés Flutter 3
Google a ainsi voulu améliorer l'expérience developpeur lors de la mise en place de Firebase sur un projet Flutter
L'idée est de s'abstraire de la récupération à la main des fichiers de configurations, etc.
Nouveautés Flutter 3
Pour cela il faudra au préalable télécharger l'utilitaire Firebase en ligne de commandes :
S'y connecter via la commande
firebase loginNouveautés Flutter 3
Ensuite, il n'y a plus qu'à suivre les étapes, et hop, tout est fait pour vous au niveau de la gestion des configurations :)
Nouveautés Flutter 3
Pour initialiser Firebase, on met en place le code qu'ils nous proposent (en récupérant firebase_core) de cette manière :
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const MyApp());
}Et maintenant ?
Et maintenant ?
Je vous l'accorde, c'était chiant.
Mettre en place le projet Firebase, télécharger les fichiers, les mettre dans les dossiers, gérer potentiellement X flavors... c'est chiant.
Mais c'est nécessaire pour commencer à travailler avec Firebase ! Et une fois que tout est en place, on peut commencer à s'amuser
Et maintenant ?
N'oubliez pas que ces étapes seront potentiellement à refaire si vous mettez en place plusieurs saveurs dans votre application avec du coup des dossiers spécifiques pour vos fichiers de configuration, des SHA-1 et des identifiants différents, etc.
Et maintenant ?
On peut enfin passer à la suite !
A partir de maintenant, vous trouverez des exemples de code complets dans le GitHub que j'ai crée pour vous :
Et maintenant ?
Chaque "chapitre" donnera lieu à un fichier Dart que vous pourrez RUN
Attention cependant : je n'ai pas versionné mes fichiers de configuration Firebase !
Du coup, ça vous oblige (si vous voulez tester) à y mettre les votre ! Mais ça vous permet surtout d'adapter ce code à n'importe quel projet Firebase :)
Firebase : des services / des dépendances
(j'avais pas trop d'idée de titre j'avoue)
Dépendances Firebase
On l'a déjà expliqué mais Firebase c'est un ensemble d'outils Cloud
Chaque outil évolue à sa vitesse et est maintenue par des équipes spécifiques chez Firebase/Google
Firebase a donc séparé les différents services en autant de dépendances différentes
Dépendances Firebase
Evidemment, les versions des dépendances et leurs implémentations changent selon les technologies
Ici nous nous basons donc sur Flutter, mais sachez que la logique générale est exactement la même à chaque fois, seule la syntaxe et l'implémentation change éventuellement
Dépendances Firebase
Si il y a une seule dépendance que vous aurez toujours, quoi qu'il arrive, c'est firebase_core
C'est le "coeur" de Firebase qui va nous permettre de lier notre app au projet Firebase
dependencies:
flutter:
sdk: flutter
firebase_core: "1.2.1"Dépendances Firebase

Quelle est la différence entre Legacy et Null safety ?
Tous les packages Firebase ne sont pas encore passés en mode null-safety !
Dépendances Firebase
Pour certains services, si vous voulez récupérer la dépendance null-safety, il vous faudra vous mettre sur la channel Beta de Flutter
A priori, c'est pas un grand risque, mais je vais pour ma part me baser sur la version Stable au cas où dans le contexte de ce cours
Dépendances Firebase
MAIS DU COUP
Il va falloir préciser à flutter que vous ne souhaitez pas compiler en mode "sound null-safety" car sinon il refusera vos dépendances Legacy Firebase

Initialisation firebase_core
Initialisation firebase_core
Avant toute chose, il va falloir initialiser Firebase pour pouvoir l'utiliser dans votre application
Il est impératif de le faire avant d'utiliser le moindre service
await Firebase.initializeApp();Initialisation firebase_core
Vous pouvez par exemple vous baser sur l'exemple proposé par FlutterFire :
import 'package:flutter/material.dart';
// Import the firebase_core plugin
import 'package:firebase_core/firebase_core.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(App());
}
class App extends StatelessWidget {
// Create the initialization Future outside of `build`:
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
@override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire:
future: _initialization,
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return SomethingWentWrong();
}
// Once complete, show your application
if (snapshot.connectionState == ConnectionState.done) {
return MyAwesomeApp();
}
// Otherwise, show something whilst waiting for initialization to complete
return Loading();
},
);
}
}Initialisation firebase_core
Ou alors... beaucoup plus simple :
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
AnalyticsManager(
analytics: FirebaseAnalytics(),
child: MaterialApp(
home: Home(),
),
),
);
}Firebase Analytics
Firebase Analytics
Pour commencer, on ajoute la dépendance
dependencies:
flutter:
sdk: flutter
firebase_core: version.recente
firebase_analytics: version.recenteFirebase Analytics
Attention, sur Android il va sûrement falloir mettre le minSDK à 19 et sur iOS, dans le PodFile, la platform à 10
Firebase Analytics
Et voilà !
Rien qu'en mettant la dépendance, beaucoup d'évènements seront loggés sur votre Dashboard

Firebase Analytics
Firebase Analytics va vous permettre de logger des évènements ainsi que des user properties
Les deux portent plutôt bien leurs noms :
- un évènement, c'est un log que l'on veut effectuer lorsque l'utilisateur effectue une action particulière (navigue d'un écran à l'autre, clique sur un bouton, etc.)
- une User Property, c'est une information permettant de catégoriser l'utilisateur en question
Firebase Analytics
Les Users Properties vont nous permettre de créer des Audiences d'utilisateurs comme par exemple : "les hommes de plus de 25 ans qui ont un abonnement premium"
Combiner les User Properties aux Évènements permet d'avoir des informations très précises sur l'utilisation de votre application par les utilisateurs et ainsi de mieux les comprendre et de mieux les cibler par la suite (re-targetting par notifications, etc.)
Firebase Analytics
Attention, je ne peux que vous déconseiller GRANDEMENT le fait de stocker dans les User Properties des informations personnelles (permettant de l'identifier) de l'utilisateur comme son nom ou adresse email
Côté RGPD, ça serait très compliqué et Google ne le recommande vraiment pas
Anonymiser les données ne vous empêche pas d'avoir quand même des audiences très précises et qualitatives !
Firebase Analytics
Maintenant qu'on a compris l'intérêt des évènements et des user properties, comment faire pour les logger ?
Pour le coup, ce n'est pas très compliqué !
Firebase Analytics
Ici, par exemple, nous loggons un évènement nommé "buy_product"
final FirebaseAnalytics analytics = FirebaseAnalytics();
analytics.logEvent(
name: 'buy_product',
parameters: {'price': 10},
);Notez que vous pouvez donner une Map de paramètres spécifique permettant de personnaliser l'évènement en question
Firebase Analytics
Ici, par exemple, nous loggons une User Property avec "role" pour clef et "admin" comme valeur
final FirebaseAnalytics analytics = FirebaseAnalytics();
analytics.setUserProperty(
name: 'role',
value: 'admin',
);Firebase Analytics
Pas très compliqué n'est-ce pas ?
Du côté de FirebaseAnalytics, vous pouvez en faire un Singleton ou utiliser le pattern InheritedWidget plus conforme à la manière de penser de Flutter
Firebase Analytics
class AnalyticsManager extends InheritedWidget {
const AnalyticsManager({
Key? key,
required Widget child,
required this.analytics,
}) : super(key: key, child: child);
final FirebaseAnalytics analytics;
static AnalyticsManager of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType(aspect: AnalyticsManager)!;
}
@override
bool updateShouldNotify(AnalyticsManager e) => false;
void logEvent({required String name, Map<String, dynamic>? parameters}) {
analytics.logEvent(name: name, parameters: parameters);
}
}Par exemple :
Firebase Analytics
Pour pouvoir l'instancier ainsi :
await Firebase.initializeApp();
runApp(
AnalyticsManager(
analytics: FirebaseAnalytics(),
child: MaterialApp(
home: Home(),
),
),
);(pour faire encore plus propre, préférez passer une interface à AnalyticsManager et donc FirebaseAnalytics pour plus de maintenabilité et resistance au changement)
Firebase Analytics
Et l'utiliser comme ça :
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: ElevatedButton(
child: Text("Click to log 'press_event'"),
onPressed: () {
AnalyticsManager.of(context).logEvent(name: "press_event");
},
),
),
),
);
}
}Firebase Analytics
Et voilà !
Par contre, ça peut mettre du temps avant que vos User Properties ou vos évènements apparaissent sur le DashBoard, pour des soucis de performances et d'optimisation réseau.. (jusqu'à 24h parfois)
Firebase Analytics
Mais on peut quand même tester :)
Pour ce faire, on va lancer la "Debug View"
Firebase nous permet d'avoir une vue en direct des évènements loggés pour pouvoir tester que tout fonctionne bien
Firebase Analytics
Pour ce faire, on doit l'activer côté Android :
adb shell setprop debug.firebase.analytics.app com.example.exampleapp(pour le désactiver ensuite :)
adb shell setprop debug.firebase.analytics.app .none.Firebase Analytics
Une fois que c'est fait, direction la Debug View sur votre projet Firebase :


Firebase Analytics
Si votre téléphone n'apparaît pas immédiatement... re-tentez, relance l'app, etc. ça bug parfois un peu honnêtement...
En tout cas, on est bon côté Firebase Analytics ! :)
Firebase Analytics
Petit exercice :
Créer une application qui affiche 3 boutons sur la Home qui permettent d'aller sur 3 écrans différents
Logger les évènements de navigation sur Analytics
Firebase Analytics
Petite correction avec l'observer de Navigation disponible sur le dépôt GitHub du cours :
Firestore -présentation
Firestore - présentation
Si vous avez déjà eu affaire à l'un des services de Firebase, je suis presque certain que c'est pour l'une de ses Bases de Données
En effet, Firebase s'est rendu très célèbre pour son concept de BDD "realtime client-first SDK"
Si nous devions résumer/traduire "realtime client-first SDK", ça donnerait :
- une BDD temps réel (pas besoin de recharger l'appel)
- on communique directement depuis le SDK mobile
- aucun serveur à maintenir ou à avoir
- performances et scalabilité au rendez-vous
- gestion de la persistence Offline
Firestore - présentation
Il existe en fait 2 services distincts de BDD qui sont :
- Firebase Realtime Database
- Firestore
Les 2 services sont des BDD NO-SQL avec les caractéristiques citées précédemment
Firestore - présentation
Petit aparté sur le NO-SQL
Pour vous, c'est quoi ?
Firestore - présentation
Le NO-SQL, ça n'a rien de nouveau ou de révolutionnaire
Une technologie de BDD NO-SQL est simplement une solution de stockage de données qui n'est pas basée sur le SQL
En effet, le SQL n'est parfois pas adapté à certaines problématiques et de nombreuses solutions NO-SQL sont apparues pour pallier ce problème
Firestore - présentation
MAIS
Le SQL reste la solution la plus pertinente dans de très nombreux cas
De plus, chaque solution NO-SQL est basé sur un stockage différent et certaines solutions sont orientées sur des problématiques précises (exemple des Graphes pour les réseaux sociaux)
Firestore - présentation
Pourquoi je précise ça ?
Parce que le NO-SQL a connu un vrai boum et certains y ont vu "une révolution" alors qu'il s'agit seulement d'autres technos
Certains développeurs font parfois le choix du NO-SQL "parce que c'est quand même mieux que ce vieux SQL"...... et ce genre de choix basé sur des arguments aussi nuls peut avoir de lourdes conséquences !
Firestore - présentation
Comparer le NO-SQL au SQL, c'est comme comparer 2 langages ou 2 Frameworks
Prendre en compte le contexte, les besoins spécifiques de votre application et des arguments logiques et scientifiques sera TOUJOURS une meilleure philosophie que de prendre "ce qui a l'air d'être le plus cool"
Firestore - présentation
BREF
Firestore - présentation
Une fois qu'on a vu tout ça.. quelles sont alors les différences entre Firestore et Realtime Database et comment choisir l'une ou l'autre des BDD ?
Au delà du fait que Firestore est plus récent, il existe un quiz sur la documentation de Firebase pour savoir quelle technologie choisir :
Firestore - présentation
A savoir aussi que les données ne sont pas du tout stockées sous le même format NO-SQL :
- Realtime Database -> Gros document JSON
- Firestore -> collections de données
Parce que c'est la solution la plus récente, qui permet de faire des requêtes plus fines, de structurer plus de données, etc. ...
Firestore - présentation
Nous étudierons dans ce cours Firestore !
Mais n'hésitez pas à vous renseigner sur Realtime Database
Firestore
Firestore
Commençons par activer le service associé dans notre console Firebase :

Firestore
Choisissez ensuite le mode test pour commencer :

Firestore
Quésako ?
Firestore est une BDD sur laquelle vous allez pouvoir envoyer des requêtes d'écritures ou de lecture depuis votre application directement : il n'y a pas d'API, vous écrivez et lisez directement dessus
Pour ce faire, vous allez simplement devoir faire référence à votre base de données.. et c'est bon ! Et ça c'est cool !
Firestore
Mais du coup, n'importe qui qui aurait cette référence, pourrait écrire ou lire ce qu'il veut dans votre base de données.... et ça c'est moins cool
C'est pour ça qu'on mettra ensuite en place des règles de sécurité sur notre BDD, mais dans un premier temps mettons nous en "mode test"
Firestore
On sélectionne ensuite la zone où nous souhaitons que les données soient stockées
Ce choix ne pourra pas être modifié donc faites-y attention

Firestore

Tout est OK niveau console Firebase !
Firestore
Ajoutons maintenant la dépendance côté Flutter pour pouvoir utiliser Firestore :
cloud_firestore: "2.2.1"Et normalement.. ça devrait être bon !
Firestore
Pour tester que tout fonctionne bien, nous allons tenter notre première écriture sur la base de données !
Je vous expliquerai plus tard le fonctionnement plus en détail, là on test juste que la connexion se fait avec un petit exemple
Firestore
Faites n'importe quelle écran avec un bouton qui lance cette méthode :
Future<void> _addUser() async {
final CollectionReference usersCollection = FirebaseFirestore.instance.collection('users');
try {
await usersCollection.add({"first_name": "John", "last_name": "Doe", "age": 42});
print("User added");
} catch (error) {
print("Failed to add user: $error");
}
}Firestore
Lancez votre application et cliquez sur le bouton, cet utilisateur a dû être renseigné dans votre Firestore !
Un problème lors de la compilation ?
Ca parle de "dex" ne pouvant exceder 64k de lignes de codes, etc. ?
On en a déjà parlé, je vous laisse voir comment régler ça dans le chapitre sur la liaison avec Android ;)
Firestore
Problème de lenteur dans le XCode Build côté iOS ?
Firestore
Avant d'aller plus loins, tâchons de comprendre de quelle manière les données sont structurées dans Firestore
Dans Firestore, nous avons des Collections qui sont des listes de Documents
Chaque Document est un objet (un peu comme en JSON) stocké au format clé/valeur et qui peut lui-même contenir des sous-collections, etc.
Firestore
Si on reprend l'exemple que vous avez développé, ça donne ça :

Firestore
Notez que Firestore crée des identifiants par défauts à vos documents mais que vous pourriez potentiellement en mettre un vous-même
Firestore
Côté création/suppression de collections :
La création du premier Document dans une Collection créera celle-ci de même que la suppression du dernier Document supprimera celle-ci
MAIS attention :
La suppression d'un Document qui comporte des sous-collections ne suppriment pas celles-ci ! Vous devrez alors les supprimer vous-même
Firestore
Pour connaître tous les types possible pour un champs d'un Document, je vous laisse voir ça
Firestore
Écrire sur la BDD
Comme nos l'avons vu dans notre exemple, la première étape est de récupérer une référence dans la BDD
Une référence de Collection par exemple :
final CollectionReference collectionReference = FirebaseFirestore.instance.collection('users');Firestore
Écrire sur la BDD
Une fois qu'on a une référence, nous pouvons ajouter un élément à cette collection ainsi :
final DocumentReference ref = await collectionReference.add({"key1": "value1", "key2", "value2"});L'identifiant de l'élément sera alors automatiquement généré par Firestore et accessible via ref.id
Firestore
Écrire sur la BDD
Pour avoir notre propre identifiant, nous pouvons remplacer l'appel à add par set, comme ceci :
await collectionReference.doc("MONID").set({"key1": "value1", "key2", "value2"});Firestore
Écrire sur la BDD
Enfin, il est possible d'écrire en profondeur dans la BDD :
Future<void> _addDummyData() async {
final CollectionReference ref = FirebaseFirestore.instance.collection('users/unidentifiant/friends');
try {
await ref.add({"first_name": "John", "last_name": "Doe", "age": 42});
print("Friend added");
} catch (error) {
print("Failed to add user: $error");
}
}Firestore
Modifier un Document
Pour modifier un Document, nous avons la méthode update qui peut être utiliser ainsi par exemple :
await collectionReference.doc("MONID").update({"first_name": "NewValue"});Firestore
Lire la BDD
Lorsque vous demandez à lire des données depuis Firestore, vous avez la possibilité de le faire de 2 manières :
- en mode one-time
- en mode realtime
Firestore
Lire la BDD
Lorsque nous allons lire une donnée, nous allons récupérer une référence sur l'élément puis récupérer une Future ou un Stream (selon le one-time ou realtime) avec le DocumentSnapshot ou le QuerySnapshot associé
Firestore
Lire la BDD
Un DocumentSnapshot, c'est l'objet qui encapsule un Document
On récupère la Map des données du Document via sa méthode data
Attention à vérifier son existence via la méthode exists car un snaphot sera toujours renvoyé sans forcément de datas
Firestore
Lire la BDD
Un QuerySnapshot, c'est l'objet qui encapsule une Collection
On y retrouve sa taille et d'autres métadata ainsi que la liste des DocumentSnapshot via le champs docs
Firestore
Lire la BDD - one-time
En mode "one-time", nous allons utiliser la méthode get pour récupérer une Future
final CollectionReference users = FirebaseFirestore.instance.collection('users');
// Pour un Document
final DocumentSnapshot snapshot = await users.doc("MONID").get();
// Pour une Collection
final QuerySnapshot snapshot = await users.get();Firestore
Lire la BDD - realtime
En mode "realtime", nous allons utiliser la méthode snapshots pour récupérer un Stream
final CollectionReference users = FirebaseFirestore.instance.collection('users');
// Pour un Document
final Stream<DocumentSnapshot> stream = users.doc("MONID").snapshots();
// Pour une Collection
final Stream<QuerySnapshot> stream = users.snapshots();Firestore
Lire la BDD - queries
Il est possible de mettre en place pas mal de finesse dans la récupération des données de votre BDD
Firestore
Lire la BDD - queries
On peut mettre
Firestore
Lire la BDD - queries
On peut également mettre en place de la pagintation !
final Query first = FirebaseFirestore.instance.collection('cities').orderBy("population").limit(25);
final QuerySnapshot firstCities = await first.get();
// ...
// On affiche notre liste graphiquement, etc.
// Lorsqu'on affiche le dernier élément, on récupère sa référence
final DocumentSnapshot lastVisible = firstCities.docs[firstCities.size - 1];
// ...
// Et on peut ensuite récupérer les 25 prochaines par exmeple
final Query second =
FirebaseFirestore.instance
.collection('cities')
.orderBy("population")
.startAfterDocument(lastVisible)
.limit(25);Firestore
Suppressions
Pour supprimer des données, rien de plus simple :
Future<void> deleteUser() async {
final CollectionReference users = await FirebaseFirestore.instance.collection('users');
try {
await users.doc('ABC123').delete();
print("User Deleted");
} catch(error) {
print("Failed to delete user: $error");
}
}Firestore
Les transactions
Imaginez que vous ayez un compteur qui peut être incrémenté par plusieurs utilisateurs en même temps (un nombre d'abonnés pour une chaîne YouTube par exemple)
Il peut arriver que plusieurs utilisateurs décident de s'abonner en même temps à la chaîne
Firestore
Les transactions
Comment s'assurer que le compteur est bien incrémenté de 2 et non pas de 1 dans le cas où vous récupériez la valeur, l'incrémenteriez puis la réécririez ?
Pour ça, on peut utiliser les Transactions, qui assurent que la valeur prise en compte sera celle présente sur le serveur au moment de l'écriture et non pas celle que vous récupérez en lecture de votre côté
Firestore
Les transactions
Exemple proposé par la documentation :
// Create a reference to the document the transaction will use
DocumentReference documentReference = FirebaseFirestore.instance
.collection('users')
.doc(documentId);
return Firestore.instance.runTransaction((transaction) async {
// Get the document
DocumentSnapshot snapshot = await transaction.get(documentReference);
if (!snapshot.exists) {
throw Exception("User does not exist!");
}
// Update the follower count based on the current count
// Note: this could be done without a transaction
// by updating the population using FieldValue.increment()
int newFollowerCount = snapshot.data()['followers'] + 1;
// Perform an update on the document
transaction.update(documentReference, {'followers': newFollowerCount});
// Return the new count
return newFollowerCount;
})
.then((value) => print("Follower count updated to $value"))
.catchError((error) => print("Failed to update user followers: $error"));Firestore
Dans le but de toujours plus faciliter la vie des développeurs, Firebase a mis en place une bibliothèque de composants UI déjà tout fait pour certains cas, par exemple pour afficher facilement une liste "infinite scroll" ou encore pour faciliter les login, etc.
Firestore
Petit exercice
Faire une petite application qui affiche une liste d'utilisateurs provenant de notre BDD Firestore
On aura un bouton flottant qui créera un utilisateur à chaque fois qu'on cliquera dessus
La liste doit automatiquement être mise à jour lorsque l'utilisateur est créé
Crashlytics
Crashlytics
Si il y a bien UN outil de la suite Firebase qu'il est INDISPENSABLE d'implémenter dans votre application, c'est celui-ci
Crashlytics est un service permettant de capter les différentes exceptions que vous ne traitez pas dans votre app et vous les affiche dans un Dashboard sur votre console Firebase
Crashlytics
Penser, en tant que développeur, qu'on a réussi à prévoir tous les bugs possibles, toutes les utilisations possibles de notre application, tous les Devices imaginables des utilisateurs, tous les cas chelous... c'est être soit idiot, soit inconscient.
Il y aura FORCÉMENT des crashs, des erreurs imprévues, etc.
Crashlytics
Grâce à Crashlytics, nous allons pouvoir analyser ceux-ci, voir leur StackTrace et donc les corriger, les marquer comme résolu et voir par la suite si ils reviennent ou non, analyser leur apparition selon les modèles de téléphones ou encore leur version d'OS, etc.
Crashlytics
N'importe quelle application mise en prod DOIT avoir une solution de reporting de crashs comme Crashlytics, l'inverse serait vraiment inconscient
Crashlytics
Déclarons donc la dépendance :
firebase_crashlytics: "version.recente"Nous avons quelques étapes de plus côté Android & iOS pour mettre en place la dépendance
Ancienne façon
Crashlytics
Android :
dans le fichier android/build.gradle, ajoutez :
dependencies {
// ... other dependencies
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.1'
}Ancienne façon
Crashlytics
Android :
dans le fichier android/app/build.gradle, ajoutez :
apply plugin: 'com.google.firebase.crashlytics'Ancienne façon
Crashlytics
iOS :
Côté iOS, on va devoir ajouter un script dans les Build Phases sur XCode
Une fois de plus, si vous n'avez pas XCode, concentrez-vous sur Android
Ancienne façon
Crashlytics
iOS :
- sur XCode, sélectionnez votre Runner
- allez sur Build Phases puis + > New Run Script Phase
- Ajoutez le code suivant dans le "Type a script"
${PODS_ROOT}/FirebaseCrashlytics/runAncienne façon
Crashlytics
Enfin, il nous reste à activer Crashlytics sur la console Firebase

Attention, il faut l'activer pour Android ET pour iOS
Ancienne façon
Crashlytics
Une fois le service ajouté, voici ce qui apparaît :

En fait, Firebase attend de nous que nous fassions un premier "Crash de test" pour lancer le service
Ancienne façon
Crashlytics
Nouveau
Désormais, tout est plus simple :
Attention à bien lancer le
flutterfire configurequi va venir changer les choses pour nous dans le build.gradle etc.
Crashlytics
Nous allons donc faire ça mais, avant, nous allons écrire le code nécessaire pour la remontée des bugs côté Flutter
Crashlytics
Tout d'abord, nous allons demander à Flutter de reporter toutes les erreurs non traitées dans Crashlytics :
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Pass all uncaught errors from the framework to Crashlytics.
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(MyApp());
}Crashlytics
Ensuite, nous allons également devoir entourer notre main dans une méthode spécifique comme ceci :
void main() {
runZonedGuarded<Future<void>>(() async {
// The following lines are the same as previously explained in "Handling uncaught errors"
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
runApp(MyApp());
}, FirebaseCrashlytics.instance.recordError);
}Crashlytics
Enfin, toujours avant de run notre App, nous allons ajouter ceci :
void main() {
runZonedGuarded<Future<void>>(() async {
//... le reste ...
Isolate.current.addErrorListener(RawReceivePort((pair) async {
final List<dynamic> errorAndStacktrace = pair;
await FirebaseCrashlytics.instance.recordError(
errorAndStacktrace.first,
errorAndStacktrace.last,
);
}).sendPort);
runApp(MyApp());
}, FirebaseCrashlytics.instance.recordError);
}Crashlytics
En conclusion, voilà la tête que devrait avoir la méthode main par rapport à l'implémentation de Crashlytics :
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runZonedGuarded<Future<void>>(() async {
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
Isolate.current.addErrorListener(RawReceivePort((pair) async {
final List<dynamic> errorAndStacktrace = pair;
await FirebaseCrashlytics.instance.recordError(
errorAndStacktrace.first,
errorAndStacktrace.last,
);
}).sendPort);
runApp(MaterialApp(home: Home()));
}, FirebaseCrashlytics.instance.recordError);
}Crashlytics
C'est bon ! On va enfin pouvoir tester !
Attention cependant, dans la doc de FlutterFire, on vous propose de tester un faux crash grâce à cette instruction :
FirebaseCrashlytics.instance.crash();Crashlytics
Ça ne fonctionne en revanche pas toujours, je vous conseille donc de lancer une bonne vielle Exception du style :
void _crash() {
throw Exception("WAHOU, voici un beau test de bug !");
}Crashlytics
Allez-y, faites le test, tout devrait être bon :)
Attention, les crashs ne sont reportés que lors du prochain redémarrage de l'application
Une fois le crash provoqué, quittez l'app, relancez là, et voilà :

Crashlytics
Pour aller plus loin, on peut également logger nos propres exceptions non-fatales :
try {
// On teste quelque chose
throw Exception("Aie, quelque chose s'est mal passé");
} catch (error, stacktrace) {
await FirebaseCrashlytics.instance.recordError(
error,
stacktrace,
reason: 'a non-fatal error'
);
}Messaging
Messaging
Grâce à Firebase Cloud Messaging, nous allons pouvoir envoyer des notifications ciblées à nos utilisateurs !
Ce sont les fameuses "notifications push" dont nous avons déjà parlé plus tôt
Messaging
Pour ce faire, déclarons la dépendance :
firebase_messaging: "version.recente"Côté iOS, il reste un certain nombre de choses à faire, propre aux signatures des applications iOS, que vous trouverez
Messaging
Ensuite, il est important de comprendre que les applications ont 3 états différents possibles :
- Foreground : l'application est ouverte et utilisée
- Background : Ouverte mais en background
- Terminated : Téléphone verrouillé ou app fermée
Messaging
Il faudra demander la permission à l'utilisateur pour pouvoir lui envoyer des notifications
Pour ce faire, voici un example de code :
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}');Messaging
Le AuthorizationStatus peut avoir alors différentes valeurs :
- authorized : c'est OK, l'utilisateur a accepté
- denied : l'utilisateur a refusé
- notDetermined : l'utilisateur n'a pas choisi
- provisional : autorisation provisoire
Messaging
En fait, côté Android, le statut sera toujours Authorized
Cependant l'instruction est tout de même obligatoire, mettez là donc pour les 2 OS :
Messaging
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
super.initState();
_initFirebaseCloudMessaging();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Center(child: Text("Un magnifique écran pour recevoir des notifations")),
);
}
void _initFirebaseCloudMessaging() async {
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}');
}
}Messaging
OK, on peut faire nos premiers tests, direction la console Firebase > Cloud Messaging, et lancez votre première notification !
Attention, le comportement par défaut est que la notification n'apparaîtra dans le centre de notifications que si l'application est en Background ou Terminated
Messaging
Ce comportement est dû au fait que vous n'avez peut-être pas envie de la même expérience si l'utilisateur est justement en train d'utiliser l'application
Il est toutefois parfaitement possible de se mettre à l'écoute de telle notifications et de réagir à leur réception
Messaging
Pour ce faire, nous écoutons les notifs comme ceci :
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?.title}');
}
});Messaging
Vous pouvez de nouveau tester votre notif en gardant votre application en Foreground, et vous arriverez à l'afficher dans vos logs
Messaging
C'est cool, mais comment faire si on veut que notre notification s'affiche dans le centre de notifications, même si l'app est en Foreground ?
Côté iOS, c'est plutôt simple :
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads up notification
badge: true,
sound: false,
);Messaging
Côté Android, c'est légèrement plus tricky
On l'a vu juste avant : on arrive à récupérer les infos de la notification
Ce qu'on va vouloir faire maintenant, c'est utiliser un package spécifique (flutter_local_notifications) pour afficher une notification locale lorsque nous recevons la notification push en mode Foreground (relisez cette phrase 2-3 fois, mais promis ça veut dire quelque chose)
Messaging
Ok, déclarons d'abord la dépendance :
flutter_local_notifications: "5.0.0+4"(Attention, ne prenez pas au dessus de 6.0.0 si vous êtes à une version de Flutter < 2.2)
Messaging
Ensuite, il nous faut initialiser le package
void _initLocalNotifications() async {
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
);
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: _onNotificationClicked,
);
}Messaging
Puis, lors de la réception d'une notification en Foreground, nous allons utiliser le package pour :
- créer une Channel avec une forte priorité
- envoyer une notification locale sur cette Channel
Messaging
Voici le code correspondant que je vais vous expliquer :
FirebaseMessaging.onMessage.listen((message) => _onMessage(message, onForeground: true));void _onMessage(RemoteMessage message, {bool onForeground = false}) async {
final AndroidNotification? android = message.notification?.android;
final RemoteNotification? notification = message.notification;
if (notification != null && android != null && onForeground) {
const AndroidNotificationChannel channel = AndroidNotificationChannel(
'high_importance_channel',
'High Importance Notifications',
'This channel is used for important notifications.',
importance: Importance.max,
);
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(channel);
flutterLocalNotificationsPlugin.show(
notification.hashCode,
notification.title,
notification.body,
NotificationDetails(
android: AndroidNotificationDetails(
channel.id,
channel.name,
channel.description,
icon: android.smallIcon,
),
),
);
}
}Messaging
On est bon ! Vous pouvez tester !
Désormais, lorsqu'une notification est reçue en Foreground, sur Android, vous lancerez l'équivalent et elle apparaîtra dans le centre de notifications du téléphone
Messaging
Nous voilà déjà bien avancés, mais on va voir encore quelques trucs :
- savoir quand une notification a ouvert l'app
- récupérer le Token de l'appareil
- enregistrer des Devices de test côté Firebase
Messaging
Parfois, votre application sera ouverte depuis une notification, c'est le comportement de base lorsque celle-ci est cliquée
Pour être au courant d'un tel comportement, vous pouvez implémenter cette callback :
FirebaseMessaging.onMessageOpenedApp.listen(_onNotificationOpenedApp);void _onNotificationOpenedApp(RemoteMessage message) async {
print("Une notification a ouvert l'application ! ${message.notification?.title}");
}Messaging
Ensuite, nous allons nous intéresser au FCM Token
Il faut savoir que chaque Device possède un FCM Token qui lui est propre
Cet identifiant permet de cibler un ou plusieurs téléphones lorsque nous souhaitons envoyer une notification
Messaging
Grâce à ce Token, nous pourrons faire 2 choses intéressantes :
- déclarer des Devices de test sur Firebase
- l'envoyer à notre API pour que celle-ci puisse, à certaines occasions, envoyer des notifications aux utilisateurs
Messaging
Pour récupérer le Token, ça se passe comme ça :
void _lookForMessagingToken() async {
String? token = await FirebaseMessaging.instance.getToken();
print("Firebase Messaging Token : $token");
FirebaseMessaging.instance.onTokenRefresh.listen((String token) {
print("Firebase Messaging Token : $token");
});
}Il est possible que Firebase rafraîchisse le Token (même si c'est rare), c'est pourquoi nous nous abonnons à ses changements
Messaging
Et... voilà !
Si vous regardez dans vos logs, vous avez maintenant le Token correspondant à votre Device !
À vous de voir ce que vous voulez en faire par rapport à votre API, moi je vais vous montrer comment s'en servir sur Firebase
Messaging
Ça se passe ici :

Messaging
Ça se passe ici :

Renseignez votre Token, et c'est bon !
Remote Config
Remote Config
Le principe derrière Remote Config est d'une facilité déconcertante :
Firebase stocke un fichier Json sur ses serveurs et vient le récupérer à chaque fois que vous lancez l'app... pour vous fournir ce qu'il contient
Là, dis comme ça, ça paraît super con
Remote Config
Dans un premier temps : OUI, c'est très "simple" et c'est quelque chose qu'on pourrait faire faire par notre API
Mais :
- on a pas forcément d'API (Firebase c'est sensé être Serverless justement)
- on a pas forcément accès à cette API (pas la même équipe)
- etc.
Remote Config
Mais aussi :
Remote config permet de ne propager les données qu'à certains utilisateurs ! Ca permet de mettre en place de l'AB testing assez facilement
Et ça... c'est déjà plus compliqué à mettre en place sur une API perso
Remote Config
Qu'est-ce qu'on met dans un Remote Config ?
- des données A/B testables
- des données qu'on veut pouvoir ajouter/retirer sans mise à jour (bouton spécifique)
- des cas d'urgences (popup bloquante)
Remote Config
Allez, c'est parti, on le met en place :
firebase_remote_config: "^0.10.0+1"Remote Config
Ok, ensuite, on va récupéré les informations du serveur :
final bool updated = await RemoteConfig.instance.fetchAndActivate();
if (updated) {
// the config has been updated, new parameter values are available.
} else {
// the config values were previously updated.
}Le code est plutôt clair mais en gros : updated est true si de nouvelles valeurs ont été récupérées et faux sinon
Remote Config
Mettons en place 2 configurations :

Remote Config
minBuildNumber :
Numéro de version minimale que doit avoir l'utilisateur, sinon on affiche la popup bloquante
blackButton :
Par exemple ici, si c'est true, on fait un certain design (noir) sinon on en fait un autre
Remote Config

Bien sûr, il faut ensuite publier les changements !
Remote Config
OK, maintenant on récupère les infos côté app, et on en fait quelque chose !
Par exemple avec le booléen :
final RemoteConfig remoteConfig = RemoteConfig.instance;
final bool updated = await remoteConfig.fetchAndActivate();
final bool blackButton = remoteConfig.getBool("blackButton");Remote Config

Remote Config
ElevatedButton(
child: Text("Go to screen a"),
style: ElevatedButton.styleFrom(
primary: blackButton ? Colors.black : Colors.white,
),
onPressed: () {
},
),Remote Config

Et voilà !
Remote Config
Pour confirmer tout ça, changeons la valeur côté Firebase :

Remote Config
Cependant, pour des raisons de performances, Firebase ne va pas venir récupérer tout le temps les mises à jour
Il est toutefois possible de configurer ça côté app Flutter !
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: Duration(seconds: 10),
minimumFetchInterval: Duration(hours: 1),
));Remote Config
Pour le test, je vais mettre un temps d'interval très cours

Et voilà !!
Remote Config
Et la popup bloquante dans tout ça ?
Je vous laisse le faire vous-même !
Je corrigerai après :)
Petit indice, vous aurez besoin du package
Remote Config
Petit indice, vous aurez besoin du package
showGeneralDialog(
context: context,
barrierDismissible: false,
pageBuilder: (context, animation1, animation2) {
return Dialog(
child: Container(
height: 300,
color: Colors.white,
child: Center(
child: Text(
"ok",
style: Theme.of(context).textTheme.button,
),
),
),
);
},
);Pour afficher une dialog
Firebase Storage
Firebase Storage
Qu'est-ce que Firebase Storage ?
C'est le service de "Bucket" de documents de Firebase
On trouve des "Buckets" chez AWS, Google, Azure, etc. Il s'agit de services cloud de stockage de photos, vidéos et tout type de documents
Firebase Storage
L'intérêt ?
Stocker et distribuer des documents est une problématique qui peut devenir très compliquée sur sa propre API
Utiliser ce genre de services permet de s'assurer de la scalabilité des services, de la sécurité des transferts, de leur performance, etc.
Firebase Storage
OK, c'est parti :
firebase_storage: "^8.1.1"Côté plateforme Firebase, il faut également activer notre Bucket Firebase Storage
Firebase Storage
Pour récupérer l'instance de Storage, ça se passe un peu comme pour tous les autres services :
final FirebaseStorage firebaseStorage = FirebaseStorage.instance;Firebase Storage
Ensuite, on peut très facilement récupérer les références de nos fichiers comme ceci, un peu à la manière de Firestore :
final Reference reference = firebaseStorage.ref("/notes.txt");Et, comme Firestore, chercher plus profondément :
final Reference reference = firebaseStorage.ref("/images/profilePicture.png");Firebase Storage
Ou encore :
firebaseStorage
.ref()
.child('images')
.child('profilePicture.png');Firebase Storage
On a également moyen de récupérer la liste des documents dans un dossier :
final ListResult result = await firebaseStorage.ref().listAll();
result.items.forEach((Reference ref) {
print('Found file: $ref');
});(Bon, après, ça prend du temps ça !)
Firebase Storage
Côté mobile, outre les documents, ce qui nous intéresse 99% du temps c'est : stocker une image ou afficher une image depuis une URL
En vrai, on ne "télécharge" jamais l'image nous même, on donne une URL fournie par l'API à un composant qui est capable d'afficher l'image correspondante avec gestion du placeholder et de l'erreur le cas échéant
Firebase Storage
Déjà, côté URL, on a cette instruction qui nous permet d'avoir l'URL associée pour un document sur Storage :
final String url = await firebaseStorage
.ref('users/123/avatar.jpg')
.getDownloadURL();
Firebase Storage
Ok, maintenant, comment upload une image ?
final String url = await firebaseStorage
.ref('users/123/avatar.jpg')
.getDownloadURL();
Firebase Storage
Ok, maintenant, comment upload une image ?
Pour ce faire, il nous faut récupérer un chemin absolu vers l'image sur le téléphone, pour ce faire on peut par exemple utiliser le package
Ensuite, ça donne quelque chose comme ça :
final Directory appDocDir = await getApplicationDocumentsDirectory();
final String filePath = '${appDocDir.absolute}/file-to-upload.png';
final File file = File(filePath);
try {
await firebaseStorage.ref('uploads/file-to-upload.png').putFile(file);
} on FirebaseException catch (e) {
// e.g, e.code == 'canceled'
}Firebase Storage
La plupart du temps, on va récupérer l'image depuis un image picker
Codons un exemple avec :
(Côté iOS, il ne faudra pas oublier de renseigner les bonnes infos dans votre Plist concernant les autorisations)
Firebase Storage
Installons le package :
image_picker: 0.8.0+3Firebase Storage
Côté code, on peut commence à s'amuser avec cette petite méthode :
Future _pickImage() async {
final pickedFile = await ImagePicker().getImage(source: ImageSource.camera);
if (pickedFile != null) {
final File file = File(pickedFile.path);
final UploadTask uploadTask = FirebaseStorage.instance.ref("imgages").putFile(file);
uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
print("Task state: ${snapshot.state}");
print("Progress: ${(snapshot.bytesTransferred / snapshot.totalBytes) * 100} %");
}, onError: (e) {
print(uploadTask.snapshot);
if (e.code == 'permission-denied') {
print('User does not have permission to upload to this reference.');
}
});
} else {
print('No image selected.');
}
}Firebase Storage
Attention, il est possible que vous vous preniez une 403
En effet, par défaut, Firebase Storage limite l'écriture sur Storage aux utilisateurs connectés (via Firebase Auth)
Ca, en théorie, c'est plutôt cool !
Mais pour notre exemple tout bête, on va retirer cette règle (pensez cependant à la remettre plus tard !)
Firebase Storage
Pour ce faire, on passe de ça :
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}à ça :
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write;
}
}
}Firebase Storage
C'est bon, vous pouvez tester l'upload, normalement tout est OK :)
Dynamic Links
Dynamic Links
Un dynamic link, qu'est-ce que c'est ?
Est-ce qu'il vous est déjà arrivé de recevoir un lien qu'un ami vous a partagé, de cliquer dessus, et d'être automatiquement redirigé vers l'app associée sur votre téléphone ?
C'est ça, un Dynamic Link !
Dynamic Links
Un Dynamic Link est un lien qui va pouvoir être interprété différemment (de manière dynamique) selon le contexte dans lequel il est ouvert
L'utilisateur a l'application ? Alors ouvrons l'application ! Sinon, envoyons le sur le Store pour qu'il la télécharge. Il est sur le web ? Envoyons le sur l'app web correspondante, ou sur la fiche de store, etc.
Dynamic Links
Vous avez peut-être déjà entendu parler de DeepLink (si c'est pas le cas, ça arrivera, promis)
Mais alors, pour vous, quelle est la différence entre un DeepLink et un Dynamic Link ?
Dynamic Links
Prenons l'example d'un lien qui ouvre votre application, déjà c'est cool
Mais imaginons maintenant que ce soit une application de News et que le lien ouvre le bon article correspondant dans l'application !
Ça, c'est un DeepLink !
Dynamic Links
On appelle DeepLink les liens qui permettent de naviguer en "profondeur" dans l'application
Chaque OS a son système de DeepLink :
- iOS : Universal Link
- Android : App Link
Dynamic Links
En gros, un Dynamic Link, c'est un lien Firebase qui encapsule un DeepLink, comme une boîte
Firebase se charge, via DynamicLink de livrer le lien au bon destinataire (app, Store, site, etc.) puis va ensuite fournir le DeepLink à l'OS qui va l'interpreter
Dynamic Links
Comment ça fonctionne ?
En fait, c'est Firebase qui va héberger votre lien
Lorsque vous cliquez, Firebase va pouvoir décider où vous envoyer puis va fournir le DeepLink
Du coup... il va falloir avoir un nom de domaine pour pouvoir héberger nos liens !
Dynamic Links
Pour ce faire, direction Hosting de Firebase, un nouveau service :)

On esquive toutes les configs en faisant "suivant"
Dynamic Links
Firebase vous fournit des noms de domaines gratuits par défaut mais vous pourrez évidemment ajouter le votre !

Dynamic Links
Ok, on peut mettre en place les dynamic links

Dynamic Links
Soit vous avez votre propre domaine (déclaré sur Hosting) soit vous en prenez un offert par Google

Dynamic Links
Nous allons créer une logique de DeepLinks en imaginant qu'ils ouvrent le détail d'un élément de notre application
Par exemple, si c'était une application de Média, nous pourrions imaginer ce genre de DeepLink :
monlink/article/[id]
Dynamic Links
Cliquez sur "nouveau lien dynamique", et créer un lien


Dynamic Links

Dynamic Links

Dynamic Links
Maintenant, nous allons voir comment recevoir un Dynamic Link côté app Flutter
(Nous ferons tout sur Android car il faut un compte Déveloper pour Apple, mais vous trouverez toutes les infos dans la doc Firebase pour les configurations Apple)
Dynamic Links
On commence par installer le package
flutter pub add firebase_dynamic_linksDynamic Links
Côté Android, on ajoute un Intent-Filter dans le Manifest pour autoriser nos Dynamic Links à ouvrir l'application
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="testflutter3.com"
android:scheme="https"/>
</intent-filter>Dynamic Links
Par définition, un Dynamic Link peut provoquer l'ouverture de l'app chez un utilisateur, il est donc important de récupérer un potentiel lien dans la méthode Main, pour être sûr de gérer celui-ci avant toute logique de l'app
Dynamic Links
Ce qui donne quelque chose comme :
final PendingDynamicLinkData? initialLink = await FirebaseDynamicLinks.instance.getInitialLink();
runApp(MyApp(initialLink));C'est bon !
Dynamic Links
Imaginons maintenant un widget de détail d'article comme celui-ci :
import 'package:flutter/material.dart';
class ArticleDetail extends StatelessWidget {
static String routeName = "/ArticleDetail";
static Future<void> navigateTo(BuildContext context, int articleId) {
return Navigator.of(context).pushNamed(routeName, arguments: articleId);
}
final int articleId;
const ArticleDetail({
Key? key,
required this.articleId,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Détail de l'article $articleId"),
),
);
}
}Dynamic Links
Imaginons une MaterialApp avec ce router :
return MaterialApp(
onGenerateRoute: (RouteSettings routeSettings) {
Widget screen = Container();
switch (routeSettings.name) {
case ArticleDetail.routeName:
if (routeSettings.arguments != null && routeSettings.arguments is int) {
screen = ArticleDetail(articleId: routeSettings.arguments as int);
}
break;
}
return MaterialPageRoute(
builder: (context) {
return screen;
},
);
},
home: Home(
pendingDynamicLinkData: pendingDynamicLinkData,
),
);Dynamic Links
Imaginons une MaterialApp avec ce router :
return MaterialApp(
onGenerateRoute: (RouteSettings routeSettings) {
Widget screen = Container();
switch (routeSettings.name) {
case ArticleDetail.routeName:
if (routeSettings.arguments != null && routeSettings.arguments is int) {
screen = ArticleDetail(articleId: routeSettings.arguments as int);
}
break;
}
return MaterialPageRoute(
builder: (context) {
return screen;
},
);
},
home: Home(
pendingDynamicLinkData: pendingDynamicLinkData,
),
);Dynamic Links
On va maintenant imaginer que notre Home est chargée de gérer les Dynamic/Deep links comme ceci :
void _initDynamicLinks() async {
if (widget.pendingDynamicLinkData != null) {
_handleDeepLink(widget.pendingDynamicLinkData!);
}
}
void _handleDeepLink(PendingDynamicLinkData dynamicLinkData) {
final Uri link = dynamicLinkData.link;
if (link.queryParameters.containsKey("articleId")) {
try {
final int articleId = int.parse(link.queryParameters["articleId"] as String);
ArticleDetail.navigateTo(context, articleId);
} catch (error) {
print("Unable to parse article ID into INT");
}
}
}Dynamic Links
Enfin, nous pouvons également géré le fait de recevoir un Dynamic Link lorsque l'application est déjà ouverte, en Background donc :
void _initDynamicLinks() async {
if (widget.pendingDynamicLinkData != null) {
_handleDeepLink(widget.pendingDynamicLinkData!);
}
FirebaseDynamicLinks.instance.onLink.listen(_handleDeepLink).onError((error) {
/// Handle Error on opening Dynamic Link
});
}Dynamic Links
Et voilà !
Vous pouvez tester en vous envoyer le DynamicLink sur Slack ou autre, cliquez dessus sur votre téléphone et vous verrez toute la logique se mettre en place :)
Dynamic Links
Il ne nous reste plus qu'une chose à gérer : comment dynamiquement créer des liens ?
En effet, créer des liens via la plateforme Firebase est une chose, mais l'énorme utilité de ce système est que les utilisateurs puissent dynamiquement s'envoyer entre eux des liens (exemple : je veux partager avec mes amis le lieu que j'ai trouvé sur AirBnB pour nos vacances)
Dynamic Links
Toujours dans notre exemple d'articles, on va imaginer un bouton qui nous permets de "partager" un article et qui va donc générer le lien
Une fois le lien créé, il nous faudra évidemment pouvoir le partager, nous aurons donc également besoin d'un package comme celui-ci par exemple qui est très efficace :
Dynamic Links
Voici l'API disponible côté Flutter pour créer un lien et comment nous pourrions l'utiliser :
void _share() async {
final dynamicLinkParams = DynamicLinkParameters(
link: Uri.parse("https://test_flutter3.com/detail/?articleId=42"),
uriPrefix: "https://dynamicccc.page.link",
androidParameters: const AndroidParameters(
packageName: "com.example.dynamic_test",
minimumVersion: 0,
),
iosParameters: const IOSParameters(
bundleId: "com.example.dynamic_test",
appStoreId: "123456789",
minimumVersion: "1.0.1",
),
googleAnalyticsParameters: const GoogleAnalyticsParameters(
source: "twitter",
medium: "social",
campaign: "example-promo",
),
socialMetaTagParameters: SocialMetaTagParameters(
title: "Article 42",
description: "Découvrez cet article super intéressant : ",
imageUrl: Uri.parse(
"https://media.istockphoto.com/photos/spongebob-squarepants-picture-id458134167?k=20&m=458134167&s=612x612&w=0&h=0ec6DQkmc-R8NgpE4TMZaapaeTVaqTvAtrmhZd2IxgE="),
),
);
final ShortDynamicLink shortDynamicLink =
await FirebaseDynamicLinks.instance.buildShortLink(dynamicLinkParams);
final Uri shortUrl = shortDynamicLink.shortUrl;
try {
Share.share(
shortUrl.toString(),
subject: "Voici un article super intéressant",
);
} catch (error) {
print("Error sharing dynamic link : $error");
}
}Firebase
By Ecalle Thomas
Firebase
Cours de 15h sur les outils Firebase orientés développement d'application mobile
- 586