Récupérer et Ajouter des Données dans Firestore avec Flutter et StreamProvider
Objectif
- Savoir utiliser les différents services de Firebase
Tâche à réaliser
- Tout d’abord, laissez-moi vous montrer la base de données.
- Et les données seront affichées comme ceci :


Créer un projet Firebase
- Rendez-vous sur https://console.firebase.google.com
- Créez un nouveau projet
- Activez Firestore dans l'onglet Build > Firestore Database
- Dans le fichier
pubspec.yaml, ajoutez :dependencies: flutter: sdk: flutter firebase_core: ^2.0.0 cloud_firestore: ^4.0.0 provider: ^6.0.0 - Puis exécutez :
flutter pub get
Afficher les données de Firebase
Créez la classe
UserModel- Il est temps de créer la classe du modèle => user_model.dart . Nous recevrons uniquement le nom et l'âge. Seuls deux champs sont donc nécessaires.
// Classe représentant un utilisateur avec deux attributs : name et age class UserModel { // Attributs (ou propriétés) de l'utilisateur String name; int age; // Constructeur principal : permet de créer un objet UserModel avec un nom et un âge UserModel({required this.name, required this.age}); // Constructeur nommé 'fromJson' : permet de créer un objet UserModel à partir d'une Map (JSON) // Utile pour convertir les données provenant de Firestore, d'une API REST, etc. UserModel.fromJson(Map<String, dynamic> parsedJSON) : name = parsedJSON['name'], // Vérifie que 'age' est bien un int ; sinon, le convertit en int (ex : si c'est un double) age = parsedJSON['age'] is int ? parsedJSON['age'] : parsedJSON['age'].toInt(); } - Pourquoi créer une classe
UserModel? - 1. Organisation du code (Modèle)
- C’est ce qu’on appelle un modèle de données. Ça permet de représenter clairement un utilisateur dans ton application.
- 2. Facilite l’échange de données
- Quand tu reçois des données depuis Firestore ou une API (généralement sous forme de Map<String, dynamic>), il est plus propre de les convertir en objets Dart (UserModel) au lieu de manipuler directement les maps.
- 3. Lisibilité et sécurité
- Travailler avec un UserModel rend ton code plus lisible, plus typé (tu sais que name est une String, age un int) et plus sûr que d’utiliser des maps avec des clés comme ['name'] partout.
- 4. Utilisable avec Provider / StreamProvider
- Tu peux ensuite l’utiliser directement dans une StreamProvider<List<UserModel>> pour afficher une liste d’utilisateurs dans ton app.
- Il est temps de créer la classe du modèle => user_model.dart . Nous recevrons uniquement le nom et l'âge. Seuls deux champs sont donc nécessaires.
Créez un fichier
firebase_services.dart:- Partie 1: Importations
- cloud_firestore: permet d’utiliser Firestore dans l’app Flutter.
- user_model.dart: on importe la classe UserModel qu’on a créée pour convertir les données Firestore en objets Dart.
- Partie 2 : Déclaration de la classe de service
FirebaseServices: cette classe regroupe les opérations liées à Firestore (bonne pratique d’architecture)._db: instance de Firebase Firestore utilisée pour interagir avec la base (lecture, ajout, suppression…).
- Partie 3 : Méthode getUsers()
collection('user'): Accède à la collection "user" dans Firestore..snapshots(): Retourne un stream en temps réel de cette collection.- Toute modification (ajout/suppression/mise à jour) est immédiatement transmise à l’application Flutter.
.map(...): Transforme le snapshot Firestore en une liste d'objets UserModel.-
doc.data(): Récupère les données de chaque document Firestore (sous forme de Map<String, dynamic>). UserModel.fromJson(...): Utilise le constructeur qu'on a défini pour transformer la Map en objet UserModel..toList(): Convertit l’itération en une liste complète de UserModel.
// Importation de la bibliothèque Firestore de Firebase // Elle permet d’accéder aux collections et documents de la base de données Cloud Firestore import 'package:cloud_firestore/cloud_firestore.dart'; // Importation du modèle de données UserModel (représente un utilisateur avec ses propriétés) import '../models/user_model.dart'; // Déclaration de la classe de service Firebase // Elle centralise toutes les interactions avec Firestore liées aux utilisateurs class FirebaseServices { // Création d'une instance unique de Firestore // Cette instance (_db) permet d'effectuer toutes les opérations de lecture/écriture sur Firestore final FirebaseFirestore _db = FirebaseFirestore.instance; // Méthode pour récupérer les utilisateurs en temps réel depuis la collection 'user' // Elle retourne un Stream (flux) de liste d'utilisateurs de type UserModel Stream<List<UserModel>> getUsers() { // Accès à la collection 'user' dans Firestore // .snapshots() écoute les modifications en temps réel (ajout, suppression, modification) return _db.collection('user').snapshots().map( // Pour chaque snapshot (état actuel de la collection), on transforme chaque document... (snapshot) => snapshot.docs.map( // ...en objet UserModel à l'aide du constructeur fromJson (doc) => UserModel.fromJson(doc.data()), // Une fois que tous les documents sont convertis, on les transforme en liste ).toList() ); } }- Partie 1: Importations
Créez la classe
view_users_page// Importation des widgets Flutter nécessaires à l'interface utilisateur import 'package:flutter/material.dart'; // Importation du package Provider pour la gestion d’état via StreamProvider et Consumer import 'package:provider/provider.dart'; // Importation du modèle UserModel, qui représente la structure d’un utilisateur import '../models/user_model.dart'; // Importation de la classe FirebaseServices, qui contient la méthode pour récupérer les utilisateurs import '../services/firebase_services.dart'; // Définition d’un widget stateless pour afficher la liste des utilisateurs class ViewUsersPage extends StatelessWidget { // Création d’une instance de FirebaseServices pour accéder à Firestore final FirebaseServices firebaseServices = FirebaseServices(); // Constructeur avec clé optionnelle ViewUsersPage({super.key}); @override Widget build(BuildContext context) { // Utilisation de StreamProvider pour fournir un flux de List<UserModel> à l’arbre des widgets return StreamProvider<List<UserModel>>( // Source des données : appel à la méthode getUsers() qui retourne un Stream<List<UserModel>> create: (_) => firebaseServices.getUsers(), // Données initiales avant l’arrivée du flux réel initialData: [], // Scaffold : structure de base de la page child: Scaffold( // Barre d’application en haut de l’écran appBar: AppBar( elevation: 4, title: Text('Liste des utilisateurs'), // Titre de l'appBar centerTitle: true, // Centrage du titre backgroundColor: Colors.amber, // Couleur de fond ), // Body : zone principale du contenu body: Consumer<List<UserModel>>( // Consumer : écoute les données fournies par le StreamProvider (ici, List<UserModel>) builder: (context, users, _) { // Si la liste est vide, afficher un message if (users.isEmpty) { return Center(child: Text('Aucun utilisateur trouvé.')); } // Sinon, afficher une liste d’utilisateurs return ListView.builder( itemCount: users.length, // Nombre total d’éléments à afficher itemBuilder: (_, index) { final user = users[index]; // Récupérer un utilisateur à l’index donné // Affichage d’un utilisateur avec ListTile return ListTile( leading: CircleAvatar(child: Icon(Icons.person)), // Avatar avec icône title: Text(user.name), // Nom de l'utilisateur subtitle: Text('${user.age} ans'), // Âge de l'utilisateur ); }, ); }, ), ), ); } }
-
Ajouter un utilisateur dans Firestore dans Flutter
-
Étape 1 : Créer la boîte de dialogue
AddUserDialog- Créer un nouveau fichier: dans : lib/views/add_user_dialog.dart
import 'package:flutter/material.dart'; import '../models/user_model.dart'; import '../services/firebase_services.dart'; class AddUserDialog extends StatefulWidget { const AddUserDialog({super.key}); @override _AddUserDialogState createState() => _AddUserDialogState(); } class _AddUserDialogState extends State<AddUserDialog> { final _formKey = GlobalKey<FormState>(); String _name = ''; int _age = 0; final FirebaseServices _firebaseServices = FirebaseServices(); @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Ajouter un utilisateur'), content: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ // Champ nom TextFormField( decoration: const InputDecoration(labelText: 'Nom'), validator: (value) => value == null || value.isEmpty ? 'Entrez un nom' : null, onSaved: (value) => _name = value!, ), // Champ âge TextFormField( decoration: const InputDecoration(labelText: 'Âge'), keyboardType: TextInputType.number, validator: (value) => value == null || int.tryParse(value) == null ? 'Entrez un âge valide' : null, onSaved: (value) => _age = int.parse(value!), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); _firebaseServices.addUser(UserModel(name: _name, age: _age)); Navigator.pop(context); } }, child: const Text('Ajouter'), ), ], ); } }
- Créer un nouveau fichier: dans : lib/views/add_user_dialog.dart
Étape 2 : Ajouter la méthode
addUser()dans FirebaseServices// Ajouter un utilisateur Future<void> addUser(UserModel user) async { await _db.collection('user').add({ 'name': user.name, 'age': user.age, }); }Étape 3 : Ajouter un
FloatingActionButton- Modifie le Scaffold pour y inclure un
floatingActionButton:floatingActionButton: FloatingActionButton( onPressed: () { // Ouvrir une boîte de dialogue pour ajouter un utilisateur showDialog( context: context, builder: (context) => AddUserDialog(), ); }, backgroundColor: Colors.amber, child: Icon(Icons.add), ),
- Modifie le Scaffold pour y inclure un
-
-
Modifier et Supprimer un utilisateur depuis Firestore dans Flutter
- Dans cette étape il faut ajouter un id une nouvelle colonne, mais pourquoi ajouter le champ id ?
- Firestore stocke chaque document avec un identifiant unique (ID). Quand tu crées un utilisateur sans spécifier manuellement cet ID :
await FirebaseFirestore.instance.collection('user').add({...}); - Firestore génère automatiquement un ID aléatoire (comme A1bC2dEf3...).
- Mais si tu veux ensuite modifier ou supprimer un utilisateur spécifique, tu dois connaître cet ID, sinon tu ne pourras pas cibler le bon document.
- En ajoutant un champ id, tu peux :
- 1. Créer un document avec un ID que tu contrôles :
await FirebaseFirestore.instance.collection('user').doc(user.id).set(user.toJson()); - 2. Supprimer un document facilement :
await FirebaseFirestore.instance.collection('user').doc(user.id).delete(); - 3. Mettre à jour un utilisateur facilement :
await FirebaseFirestore.instance.collection('user').doc(user.id).update({ 'name': newName, 'age': newAge, });
- Dois-tu modifier le fichier JSON dans Firebase pour ajouter une nouvelle colonne (champ) ?
- Non, tu n'as pas besoin de modifier un fichier JSON dans Firebase.
- Firebase Firestore est une base de données NoSQL flexible, ce qui veut dire que tu peux ajouter un nouveau champ directement dans ton code Flutter, et Firestore va automatiquement l'enregistrer même s’il n’existait pas avant.
- 1. Créer un document avec un ID que tu contrôles :
-
Étape 1 : Modifier UserModel pour inclure l’ID du document Firestore
- Firestore identifie les documents uniquement par leur ID
- Dans Firestore, chaque document possède un ID généré automatiquement (doc.id).
- Cet ID n’est pas inclus dans les données retournées par doc.data(), il est séparé.
- Pour pouvoir modifier (update) ou supprimer (delete) un document, il faut connaître son ID.
- Donc si tu veux faire :
FirebaseFirestore.instance.collection('users').doc(user.id).delete();. Tu dois obligatoirement avoir accès à l'ID quelque part.
class UserModel { String? id; // maintenant String car Firestore génère une ID string String name; int age; UserModel({this.id, required this.name, required this.age}); factory UserModel.fromJson(Mapjson, String id) { return UserModel( id: id, name: json['name'], age: json['age'] is int ? json['age'] : int.parse(json['age'].toString()), ); } } -
Étape 2 : Ajouter les boutons Modifier & Supprimer dans la liste
- Dans ViewUsersPage, modifie le ListTile:
return ListTile( leading: const CircleAvatar(child: Icon(Icons.person)), title: Text(user.name), subtitle: Text('${user.age} ans'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () { showDialog( context: context, builder: (_) => EditUserDialog(user: user), ); }, ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () { if (user.id != null) { firebaseServices.deleteUser(user.id!); } }, ), ], ), ); Étape 3 : Adapter getUsers() pour passer l’ID
- Pourquoi
doc.idest passé isolément dans UserModel.fromJson(...) et pas dans doc.data()?(doc) => UserModel.fromJson(doc.data(), doc.id)Et non pas :(doc) => UserModel.fromJson(doc.data()) // Ça ne contient pas l'ID ! - doc.data() ne contient pas l'ID du document
- Firestore sépare l'identifiant (ID) du document de son contenu (data).
- L’ID est dans doc.id
- Donc si tu veux l’utiliser, tu dois le passer séparément.
- C’est pourquoi tu fais :
UserModel.fromJson(doc.data(), doc.id) - Tu reçois :
- doc.data() → les champs name, age, etc.
doc.id → l'identifiant unique du document dans Firestore
// Récupérer les utilisateurs en temps réel Stream- > getUsers() {
return _db.collection('user').snapshots().map(
(snapshot) => snapshot.docs.map(
(doc) => UserModel.fromJson(doc.data(), doc.id), // Utilisation de doc.id comme ID
).toList(),
);
}
- Pourquoi
Étape 4 : Ajouter la méthode
deleteUser()// Méthode pour supprimer un utilisateur // Méthode pour supprimer un utilisateur FuturedeleteUser(String userId) async { try { await _db.collection('user').doc(userId).delete(); print('Utilisateur avec id=$userId supprimé'); } catch (e) { print('Erreur lors de la suppression de l\'utilisateur : $e'); } } Étape 5 : Créer EditUserDialog
import 'package:flutter/material.dart'; import '../models/user_model.dart'; import '../services/firebase_services.dart'; class EditUserDialog extends StatefulWidget { final UserModel user; const EditUserDialog({super.key, required this.user}); @override State<EditUserDialog> createState() => _EditUserDialogState(); } class _EditUserDialogState extends State<EditUserDialog> { final _formKey = GlobalKey<FormState>(); late String _name; late int _age; @override void initState() { super.initState(); _name = widget.user.name; _age = widget.user.age; } @override Widget build(BuildContext context) { return AlertDialog( title: Text('Modifier utilisateur'), content: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( initialValue: _name, decoration: InputDecoration(labelText: 'Nom'), validator: (value) => value == null || value.isEmpty ? 'Entrez un nom' : null, onSaved: (value) => _name = value!, ), TextFormField( initialValue: _age.toString(), decoration: InputDecoration(labelText: 'Âge'), keyboardType: TextInputType.number, validator: (value) => value == null || int.tryParse(value) == null ? 'Entrez un âge valide' : null, onSaved: (value) => _age = int.parse(value!), ), ], ), ), actions: [ ElevatedButton( onPressed: () { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); FirebaseServices().updateUser( widget.user.id!, // doc ID UserModel(name: _name, age: _age), ); Navigator.of(context).pop(); } }, child: Text('Modifier'), ) ], ); } }Étape 6 : Ajouter addUser() dans FirebaseServices
// Ajouter un utilisateur FutureaddUser(UserModel user) async { await _db.collection('user').add({ 'id': user.id, // Utilisation de l'ID de l'utilisateur 'name': user.name, 'age': user.age, }); } Étape 7 : Ajouter updateUser() dans FirebaseServices
// Mettre à jour un utilisateur FutureupdateUser(String userId, UserModel user) async { await _db.collection('user').doc(userId).update({ 'id': user.id, 'name': user.name, 'age': user.age, }); } Étape 8 : Ajouter une alerte de confirmation avant suppression
- L’utilisateur doit voir une boîte de dialogue avec “Annuler” ou “Supprimer”.
- S’il confirme, on supprime via deleteUser.
- Tu peux aussi afficher un petit SnackBar en bas pour indiquer que l’utilisateur a été supprimé.
final rootContext = context; // Conserve le contexte principal ............ return ListTile( leading: const CircleAvatar(child: Icon(Icons.person)), title: Text(user.name), subtitle: Text('${user.age} ans'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit, color: Colors.blue), onPressed: () { showDialog( context: context, builder: (_) => EditUserDialog(user: user), ); }, ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Confirmation'), content: Text('Voulez-vous vraiment supprimer "${user.name}" ?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Annuler'), ), TextButton( onPressed: () async { Navigator.of(context).pop(); await firebaseServices.deleteUser(user.id!); // Utilisation du rootContext ici ScaffoldMessenger.of(rootContext).showSnackBar( SnackBar( content: const Text('Utilisateur supprimé'), behavior: SnackBarBehavior.floating, margin: const EdgeInsets.all(16), ), ); }, child: const Text('Supprimer', style: TextStyle(color: Colors.red)), ), ], ), ); }, ), ], ), );