Travailler avec Firebase Authentication dans Flutter
Sommaire
- 1- Objectif
- 2- Introduction à Firebase Authentication
- 3- Fonctionnalités principales de l'authentification Firebase
- 4- Architecture MVC en Flutter + Firestore exemple collection livre)
- 4.1- MODÈLE (models/livre.dart)
- 4.2- CONTROLLER (controllers/livre_controller.dart)
- 4.3- VUE (views/ajouter_livre.dart)
- 5- Projet à réaliser
- 6- Configuration du projet Flutter avec Firestore
- 6.1- Fichier main.dart
- 6.2- Modifier les règles Firestore
- 7- Effectuer des opérations
CRUDdans Firestore - 7.1- Règles et bonnes pratiques pour créer un document dans Firestore :
- 7.1.1- Utiliser des modèles (classes) pour représenter les données
- 7.1.2- Utiliser
add()oudoc().set()selon le besoin - 7.2- Lire un document dans Firestore:
- 7.2.1- Lire tous les documents d’une collection
- 7.2.2- Lire un document spécifique via son ID
- 7.2.3- Récapitulatif visuel
- 7.3- Méthodes de Mise à Jour Firestore:
- 7.3.1- MISE À JOUR PARTIELLE - update()
- 7.3.2- REMPLACEMENT COMPLET
-set()sur document existant - 7.3.3- MISE À JOUR AVEC FUSION -
set(merge: true) - 7.4- Supprimer un document :
- 8- 🔍 Rechercher des documents dans Firestore
- 8.1- Principe général de la recherche dans Firestore
- 8.2- Rechercher selon la valeur exacte d'un champ (Rechercher des livres par auteur)
- 8.2.1- Définition
- 8.2.2- 🔹 Lecture ponctuelle (Future)
- 8.2.3- 🔹 Recherche dynamique en temps réel (Stream)
- 8.3- Recherche avec plusieurs critères
- 8.3.1- Définition
- 8.3.2- 🔹 Exemple : auteur + année minimale
- 8.4- Recherche par mot-clé (titre ou recherche partielle)
- 8.4.1- Définition
- 8.4.2- 🔹 Exemple de structure de document
- 8.4.3- 🔹 Exemple de fonction Dart
- 8.5- Récapitulatif visuel des recherches
- 8.6- Activité
- 9- 🔒 Sécurité et règles Firestore
- 10- Activités
- 10.1.1- Cours Flutter
Travailler avec Firebase Authentication dans Flutter

-
Objectif
- Comprendre les différents modes d’authentification Firebase
- Implémenter l’authentification email/mot de passe
- Gérer les états d’authentification dans Flutter
- Sécuriser l’accès aux données Firestore
-
Introduction à Firebase Authentication
- Qu’est-ce que Firebase Authentication?
Firebase Authenticationest un service backend puissant et sécurisé fourni par Google qui permet d’authentifier les utilisateurs dans vos applications de manière simple et rapide.Firebase Authenticationest un service backend performant proposé par Google Firebase , conçu pour accélérer l’ authentification des utilisateurs dans les applications. Prenant en charge diverses méthodes d’authentification, telles que l’adresse e-mail/mot de passe, le numéro de téléphone et les connexions via les réseaux sociaux , Firebase Authentication garantit une authentification et une gestion des identités sécurisées.- En quoi consiste exactement ce service ?
- Imaginez devoir créer vous-même tout un système de :
- Création de comptes utilisateurs
- Vérification d’emails
- Réinitialisation de mots de passe
- Gestion des sessions
- Sécurité des données…
- Firebase Authentication fait tout ce travail complexe pour vous !
-
Fonctionnalités principales de l’authentification Firebase
- Plusieurs fournisseurs d’authentification : Firebase Authentication prend en charge plusieurs méthodes d’authentification, notamment l’adresse e-mail/mot de passe, le numéro de téléphone, la connexion via les réseaux sociaux (Google, Facebook, Twitter, GitHub), et bien plus encore.
- Intégration facile : Firebase Authentication fournit des SDK pour les plateformes populaires, ce qui facilite l’intégration de l’authentification dans vos applications en quelques lignes de code seulement.
- Sécurité : Firebase Authentication gère l’authentification des utilisateurs de manière sécurisée, en utilisant des protocoles de chiffrement et d’authentification conformes aux normes de l’industrie afin de protéger les données et les informations d’identification des utilisateurs.
- Gestion des utilisateurs : Firebase Authentication fournit des outils pour la gestion des comptes utilisateurs, notamment la création d’utilisateurs , la réinitialisation des mots de passe , la vérification des adresses e-mail et la suppression des comptes .
- Personnalisation : Vous pouvez personnaliser l’expérience d’authentification dans votre application, notamment en personnalisant l’ interface utilisateur , en mettant en œuvre l’ authentification multifacteurs et en appliquant des politiques de mots de passe.
-
Architecture MVC en Flutter + Firestore exemple collection livre)
- Objectif pédagogique: Comprendre comment circule l’information entre la Vue, le Contrôleur, le Modèle, et Firestore quand on ajoute un livre.
- Schéma simplifié du flux
- La logique métier correspond à ce que fait ton application, et pourquoi elle le fait, par opposition à la logique technique (affichage, gestion de la base de données, etc.).
- Exemple concret :
- Imaginons une application de banque
- Règle métier :
- « Un compte ne peut pas être à découvert de plus de 1000 DT. »
- Dans MVC :
- Model → contient la méthode retirer(argent) qui vérifie cette règle.
- Controller → reçoit la requête de l’utilisateur (« je veux retirer 200 € »), appelle la méthode retirer() du modèle et choisit la vue à afficher selon le résultat.
- View → affiche le message “Retrait réussi” ou “Découvert maximal atteint”.
- En résumé :
- La logique métier = les règles, calculs et processus propres au domaine fonctionnel de ton application.
- Elle se trouve dans le Modèle, pas dans le Contrôleur ni dans la Vue.
-
MODÈLE (models/livre.dart)
- Rôle : Définir la structure des données.
- Comprendre que le modèle est la véritable représentation du livre.
- À retenir :
- On crée une classe Livre pour garder un code propre et clair.
.toMap()permet de transformer l’objet en format compréhensible par Firestore.-
CONTROLLER (controllers/livre_controller.dart)
- Rôle : Gérer la logique métier.
- C’est le « chef d’orchestre » entre la vue et la base de données.
- À retenir :
- Le contrôleur prépare la donnée avant l’envoi.
- On y trouve souvent try/catch pour bien gérer les erreurs.
- La vue ne parle pas directement à Firestore → elle passe par le contrôleur.
-
VUE (views/ajouter_livre.dart)
- Rôle : Afficher l’interface et récupérer les données de l’utilisateur.
- À retenir :
- La vue affiche uniquement l’interface → pas de logique Firestore ici !
- Elle utilise des champs TextField pour capturer les données.
- Au clic, elle crée un Livre puis appelle une méthode du controller.
-
Projet à réaliser
- Créer une application mobile permettant de gérer une collection de livres avec :
-
Configuration du projet Flutter avec Firestore
-
Fichier main.dart
-
Modifier les règles Firestore
- Allez dans la Console Firebase → Firestore Database → Règles
- REMPLACEZ les règles actuelles par :
-
Effectuer des opérations
CRUDdans Firestore - Firestore vous permet d’effectuer des opérations de création, de lecture, de mise à jour et de suppression
(CRUD) sur vos données. Voyons comment procéder dans Flutter. -
Règles et bonnes pratiques pour créer un document dans Firestore :
-
Utiliser des modèles (classes) pour représenter les données
- Toujours créer une classe Livre (ou autre) pour représenter un document :
- Avantage : cohérence, lisibilité du code, moins d’erreurs lors du stockage.
-
Utiliser
add()oudoc().set()selon le besoin - Dans Firestore, vous pouvez créer un document dans une collection de deux manières :
- Avec
.add()➜ Id auto-généré par Firestore : - Avec
.doc(id).set()➜ Id personnalisé : - Vous choisissez vous-même l’ID du document.
- Utile quand vous devez garantir l’unicité ou utiliser un ID déjà connu.
- Donne un meilleur contrôle sur la structure de votre base de données.
- Toujours gérer les exceptions
- Exemple :
-
Lire un document dans Firestore:
- Firestore propose 2 niveaux de lecture :
- Document spécifique (via son ID)
- Tous les documents d’une collection
- Et 2 modes de récupération :
- En continu (temps réel) avec snapshots() ➜ Retourne un Stream
- Une seule fois (ponctuel) avec get() ➜ Retourne un Future
-
Lire tous les documents d’une collection
- En temps réel (Stream)
- Mise à jour automatique des données dès qu’un changement a lieu.
- Utilisation typique :
- Afficher une liste de livres dans un StreamBuilder.
- Utile pour un flux dynamique d’informations (ex : messages chat, liste mise à
jour en direct). - Lecture ponctuelle (Future)
- Lecture «
one shot« , uniquement au moment où le code est exécuté. - Utilisation typique :
- Charger une liste statique, non mise à jour automatiquement.
- Exemple : Exporter une liste d’objets dans un fichier Excel.
-
Lire un document spécifique via son ID
- En temps réel (Stream)
- Pour suivre les changements en direct sur un seul document.
- Utilisation typique :
- Suivre l’état d’un document en particulier (ex : stock en temps réel, statut de
commande). - Lecture ponctuelle (Future)
- Lecture ciblée, destinée à une action ponctuelle ou une navigation.
- C’est idéal pour récupérer les données d’un seul document lors d’une navigation ou
d’une action spécifique, comme ouvrir une page de détails pour un livre. - Il utilise
await ... .get()→ cela indique une lecture une seule fois,
au moment précis où tu appelles la fonction. - Utilisation typique :
- Récupérer les détails d’un élément lors d’une navigation vers une nouvelle page.
-
Récapitulatif visuel
Utilisateur ➜ View (Formulaire) ➜ Controller ➜ Model ➜ Firestore
⬆︎ ⬆︎ ⬆︎
Affichage Logique MÉTIER Données stockées
Logique métier
class Livre {
final String titre;
final String auteur;
final int annee;
Livre({required this.titre, required this.auteur, required this.annee});
// Convertit l'objet en Map pour Firestore
Map toMap() {
return {
'titre': titre,
'auteur': auteur,
'annee': annee,
};
}
}
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/livre.dart';
class LivreController {
final CollectionReference _livresRef =
FirebaseFirestore.instance.collection('livres');
Future ajouterLivre(Livre livre) async {
try {
await _livresRef.add(livre.toMap());
print("Livre ajouté avec succès !");
} catch (e) {
print("Erreur lors de l'ajout: $e");
}
}
}
import 'package:flutter/material.dart';
import '../models/livre.dart';
import '../controllers/livre_controller.dart';
class AjouterLivreView extends StatelessWidget {
final _titreController = TextEditingController();
final _auteurController = TextEditingController();
final _anneeController = TextEditingController();
final LivreController livreController = LivreController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Ajouter un Livre")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(controller: _titreController, decoration: InputDecoration(labelText: "Titre")),
TextField(controller: _auteurController, decoration: InputDecoration(labelText: "Auteur")),
TextField(controller: _anneeController, decoration: InputDecoration(labelText: "Année"), keyboardType: TextInputType.number),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Livre livre = Livre(
titre: _titreController.text,
auteur: _auteurController.text,
annee: int.parse(_anneeController.text),
);
livreController.ajouterLivre(livre);
},
child: Text("Ajouter"),
),
],
),
),
);
}
}
-
✅ Ajouter de nouveaux livres
📖 Consulter la liste des livres
✏️ Modifier les informations d’un livre
🗑️ Supprimer des livres
🔍 Rechercher des livres
Architecture MVC imposée
lib/
├── main.dart # Point d'entrée de l'application
├── firebase_options.dart # Configuration Firebase
├── models/
│ └── livre_model.dart # Modèle de données
├── controllers/
│ └── livre_controller.dart # Logique métier & Firestore
└── views/
├── liste_livres_view.dart # Écran principal
└── ajouter_livre_view.dart # Formulaire d'ajout
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialisation Firebase avec la configuration spécifique
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform, // ← Utilise notre config
);
runApp(MyApp());
}
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// RÈGLES POUR DÉVELOPPEMENT (À CHANGER EN PRODUCTION)
match /livres/{document} {
allow read, write: if true; // Autorise tout le monde
}
// OU pour tester avec authentification :
// match /livres/{document} {
// allow read, write: if request.auth != null;
// }
}
}
class Livre {
final String titre;
final String auteur;
final int annee;
Livre({required this.titre, required this.auteur, required this.annee});
Map toMap() {
return {
'titre': titre,
'auteur': auteur,
'annee': annee,
};
}
}
await FirebaseFirestore.instance
.collection('livres')
.add(livre.toMap());
await FirebaseFirestore.instance
.collection('livres')
.doc(livreId)
.set(livre.toMap());
try {
await _livresRef.add(livre.toMap());
} catch (e) {
print("Erreur lors de l'ajout: $e");
}
Important : ne jamais laisser une erreur Firestore passer en silence.
Stream, doc.id);
}).toList();
});
}
Future<List<Livre>> getTousLesLivres() async {
try {
final snapshot = await _livresRef.get();
return snapshot.docs
.map((doc) => Livre.fromFirestore(doc.data() as Map<String, dynamic>, doc.id))
.toList();
} catch (e) {
throw 'Erreur lors de la récupération : $e';
}
}
Stream<Livre?> getLivreStreamById(String id) {
return _livresRef.doc(id).snapshots().map((doc) {
if (doc.exists) {
return Livre.fromFirestore(doc.data() as Map<String, dynamic>, doc.id);
}
return null;
});
}
Future<Livre?> getLivreById(String id) async {
try {
final doc = await _livresRef.doc(id).get();
if (doc.exists) {
return Livre.fromFirestore(doc.data() as Map<String, dynamic>, doc.id);
}
return null;
} catch (e) {
throw 'Erreur lors de la récupération: $e';
}
}
| Lecture | Cible | Mode | Retourne | Exemples |
|---|---|---|---|---|
.collection().snapshots() |
Tous les documents | Temps réel | Stream<List> | Liste des livres qui se met à jour automatiquement |
.collection().get() |
Tous les documents | Ponctuel | Future<List> | Exporter tous les livres une seule fois |
.doc(id).snapshots() |
Document spécifique | Temps réel | Stream | Observer l’état d’un seul livre (ex : stock, statut) |
.doc(id).get() |
Document spécifique | Ponctuel | Future | Lire un livre quand on en a besoin (page détail) |
Méthodes de Mise à Jour Firestore:
-
MISE À JOUR PARTIELLE – update()
- Concept : Modifier seulement certains champs d’un document existant
- MISE À JOUR D’UN SEUL CHAMP
- MISE À JOUR DE PLUSIEURS CHAMPS
-
REMPLACEMENT COMPLET
-set()sur document existant - Concept : Remplacer TOUT le document existant
-
MISE À JOUR AVEC FUSION –
set(merge: true) - Concept : Mettre à jour certains champs sans supprimer les autres
- Exemple—>AVANT/APRÈS :
await FirebaseFirestore.instance
.collection('livres')
.doc('abc123') // Document existant
.update({
'titre': 'Nouveau Titre' // Seul ce champ sera modifié
});
await FirebaseFirestore.instance
.collection('livres')
.doc('abc123')
.update({
'titre': 'Titre Modifié',
'auteur': 'Nouvel Auteur',
'annee': 2024
});
await FirebaseFirestore.instance
.collection('livres')
.doc('abc123') // Document doit EXISTER
.set({
'titre': 'Nouveau Titre',
'auteur': 'Nouvel Auteur',
'annee': 2024
// Tous les anciens champs sont remplacés
});
await FirebaseFirestore.instance
.collection('livres')
.doc('abc123')
.set({
'titre': 'Titre Modifié',
'auteur': 'Auteur Modifié'
}, SetOptions(merge: true)); // ← Les autres champs sont conservés
{
"titre": "Ancien Titre",
"auteur": "Auteur Original",
"annee": 2000,
"genre": "Roman"
}
// APRÈS set() avec merge: true
{
"titre": "Titre Modifié", // ← Modifié
"auteur": "Auteur Modifié", // ← Modifié
"annee": 2000, // ← Conservé !
"genre": "Roman" // ← Conservé !
}
Supprimer un document :
- Pour supprimer un document, vous pouvez utiliser la méthode ‘delete’ sur une référence de
document. Par exemple :
Firestore.instance.collection('collection').document('document').delete();
🔍 Rechercher des documents dans Firestore
- Firestore permet d’effectuer des recherches filtrées sur les collections grâce aux requêtes (
Query). - Ces requêtes servent à extraire uniquement les documents correspondant à certains critères (par exemple : auteur, année, mot-clé…).
-
Principe général de la recherche dans Firestore
- Firestore ne fonctionne pas comme une base SQL :
- Il n’y a pas de
SELECT * FROM ... WHERE ... LIKE. - À la place, on utilise des requêtes structurées avec la méthode
.where(). - Les requêtes Firestore :
- Filtrent les documents selon des champs spécifiques,
- Peuvent être ponctuelles (
get()) ou en temps réel (snapshots()), - Nécessitent parfois des index pour plusieurs conditions.
-
Rechercher selon la valeur exacte d’un champ (Rechercher des livres par auteur)
-
Définition
-
Recherche avec plusieurs critères
-
Définition
-
Recherche par mot-clé (titre ou recherche partielle)
-
Définition
-
Récapitulatif visuel des recherches
-
Activité
- Ajouter une zone de recherche dans ton interface pour filtrer les livres en temps réel selon le titre saisi.
- Dans une petite application, il est plus ergonomique d’intégrer la recherche dans la liste principale.
Cependant, dans une application plus complexe (ex : librairie en ligne), il est préférable d’avoir une page de recherche dédiée, pour gérer plusieurs filtres et une interface plus riche. - Étapes globales
- Transformer
ListeLivresViewenStatefulWidget(car la recherche change dynamiquement). - Ajouter un
TextField(champ de recherche) dans la page. - Utiliser
livreController.rechercherLivres(query)quand le champ n’est pas vide, sinon afficher la liste complète avecgetLivresStream().
Cette recherche consiste à filtrer les documents selon la valeur exacte d’un champ.
Exemple : retrouver tous les livres écrits par Victor Hugo.
🔹 Lecture ponctuelle (Future)
Lecture « one-shot », exécutée une seule fois au moment de l’appel.
Future<List<Livre>> rechercherLivresParAuteur(String auteur) async {
try {
final snapshot = await FirebaseFirestore.instance
.collection('livres')
.where('auteur', isEqualTo: auteur) // Filtre sur le champ 'auteur'
.get(); // Lecture ponctuelle
// Conversion des documents Firestore en objets Livre
return snapshot.docs.map((doc) {
return Livre.fromFirestore(doc.data() as Map<String, dynamic>, doc.id);
}).toList();
} catch (e) {
throw 'Erreur lors de la recherche : $e';
}
}
🧭 Utilisation typique :
Afficher la liste des livres d’un auteur donné.
Exemple : l’utilisateur saisit « Victor Hugo » dans un champ de recherche.
🔹 Recherche dynamique en temps réel (Stream)
Lecture en continu, les résultats sont automatiquement mis à jour lorsqu’un changement a lieu dans Firestore.
Stream<List<Livre>> rechercherLivresStream(String auteur) {
return FirebaseFirestore.instance
.collection('livres')
.where('auteur', isEqualTo: auteur)
.snapshots() // Écoute en temps réel
.map((snapshot) {
return snapshot.docs.map((doc) {
return Livre.fromFirestore(doc.data() as Map<String, dynamic>, doc.id);
}).toList();
});
}
🧭 Utilisation typique :
Rafraîchir automatiquement la liste affichée à l’écran.
Exemple : affichage avec un StreamBuilder dans Flutter.
Tu peux combiner plusieurs conditions (where) pour affiner la recherche.
⚠️ Firestore impose certaines limites (souvent un index combiné est requis).
🔹 Exemple : auteur + année minimale
Future<List<Livre>> rechercherLivresAvances(String auteur, int anneeMin) async {
try {
final snapshot = await FirebaseFirestore.instance
.collection('livres')
.where('auteur', isEqualTo: auteur)
.where('annee', isGreaterThanOrEqualTo: anneeMin)
.get();
return snapshot.docs.map((doc) {
return Livre.fromFirestore(doc.data() as Map<String, dynamic>, doc.id);
}).toList();
} catch (e) {
throw 'Erreur lors de la recherche avancée : $e';
}
}
🧭 Utilisation typique :
Rechercher tous les livres de Victor Hugo publiés après 1850.
Utile pour des filtres combinés (par année, catégorie, auteur…).
Firestore ne prend pas en charge les recherches textuelles floues (contains, startsWith, etc.) comme SQL.
➡️ Solution : créer un champ d’index de mots-clés (keywords) dans chaque document.
🔹 Exemple de structure de document
{
"titre": "Les Misérables",
"auteur": "Victor Hugo",
"annee": 1862,
"keywords": ["les", "misérables", "les misérables", "victor hugo"]
}
Chaque document contient un tableau keywords utilisé pour la recherche.
🔹 Exemple de fonction Dart
Future<List<Livre>> rechercherParMotCle(String mot) async {
try {
final snapshot = await FirebaseFirestore.instance
.collection('livres')
.where('keywords', arrayContains: mot.toLowerCase())
.get();
return snapshot.docs.map((doc) {
return Livre.fromFirestore(doc.data() as Map<String, dynamic>, doc.id);
}).toList();
} catch (e) {
throw 'Erreur lors de la recherche par mot-clé : $e';
}
}
🧭 Utilisation typique :
Recherche « intelligente » dans une barre de recherche.
Exemple : taper “mis” retourne Les Misérables grâce au champ keywords.
| Type de recherche | Méthode Firestore | Mode | Retourne | Exemple d’usage |
|---|---|---|---|---|
| Filtrage simple | .where('auteur', isEqualTo: ...) |
Ponctuel (get()) |
Future<List> |
Trouver tous les livres d’un auteur |
| Filtrage numérique | .where('annee', isGreaterThanOrEqualTo: ...) |
Ponctuel ou Stream | Liste filtrée | Livres publiés après une certaine année |
| Temps réel | .snapshots() |
Stream | Stream<List> |
Résultats mis à jour automatiquement |
| Recherche par mot-clé | .where('keywords', arrayContains: ...) |
Ponctuel | Liste filtrée | Recherche partielle sur le titre |
🔒 Sécurité et règles Firestore
- Les règles Firestore contrôlent qui peut lire et écrire des données.
- Exemple de règles basiques :
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
Activités
- Extraire la partie d’affichage de la liste (
ListView.builder) dans un widget réutilisable appelé par exemple : ListeLivresWidget
