Comment créer sa base de données Firebase pour Flutter ?
Sommaire
- 1- Objectifs
- 2- Pré-requis
- 3- Fonctionnement d'une base de données Firebase
- 4- Créer une base de données Firestore
- 5- Ajouter vos données
- 6- Nous pouvons maintenant passer à la partie codage
- 6.1- Importation des dépendances
- 6.2- Fonction principale
- 6.3- Classe MyApp
- 6.4- Écran principal (Utilisateurscreen)
- 6.5- État de Utilisateurscreen
- 6.5.1- Variables principales
- 6.5.2- Fonction d'ajout
- 6.5.3- Fonction de mise à jour
- 6.6- Fonction de suppression
- 6.7- Formulaire (Ajout/Modification)
- 6.8- Construction de l'interface utilisateur
- 7- Utiliser un ID personnalisé dans Firestore (facultatif)
- 7.1- Pourquoi utiliser un ID personnalisé ?
- 7.2- Structure de la fonction avec ID personnalisé
- 7.3- Réalisation
- 7.4- Comment appeler cette fonction ?
- 8- Générer un ID entier dynamique :
- 8.1- Utilisation de la méthode
Reduce
: - 8.1.1- Étapes pour générer un ID entier dynamique :
- 8.1.2- Implémentation de la méthode
- 8.1.3- Utilisation dans l'application :
- 8.2- Utilisation de la méthode avec transaction
- 8.2.1- Etapes de réalisation
- 8.2.2- Implémentation de la méthode
- 8.2.3- Cours Flutter
Comment créer sa base de données Firebase pour Flutter ?
-
Objectifs
- L’objectif de ce tutoriel est de guider pas à pas les étudiants sur la création et la configuration d’une base de données Firebase pour une application Flutter. À la fin de ce tutoriel, vous serez capable de :
- Créer un projet Firebase.
- Configurer une base de données Firebase (Realtime Database ou Firestore).
- Connecter votre application Flutter à cette base de données.
-
Pré-requis
- Une application Flutter fonctionnelle.
- Un compte Google pour accéder à Firebase.
- Flutter et Firebase CLI correctement installés sur votre ordinateur.
-
Fonctionnement d’une base de données Firebase
- 3 notions sont à connaître pour comprendre l’organisation d’une base de données Firebase :
- 1. Collections
- Qu’est-ce qu’une collection ? Une collection est un ensemble d’objets similaires regroupés. Elle agit comme un conteneur et regroupe des documents ayant des informations relatives entre elles.
- Exemple : Une collection Users pour stocker les informations des utilisateurs.
- 2. Documents
- Qu’est-ce qu’un document ? Un document est une unité de données individuelle à l’intérieur d’une collection. Chaque document est identifié par un ID unique dans la collection.
- Exemple : Un document dans la collection Users représente un utilisateur individuel. Ce document peut avoir des champs comme name, age, email.
- Sous-collections : Les documents peuvent contenir des sous-collections pour structurer davantage les données.
- Exemple : Un utilisateur (document) peut avoir une sous-collection Orders pour suivre ses commandes.
- 3. Champs
- Qu’est-ce qu’un champ ? Les champs sont les paires clé-valeur à l’intérieur d’un document. Chaque champ a un nom unique et une valeur.
- Exemple : Le document d’un utilisateur peut contenir des champs comme name: « Alice », age: 25, email: « alice@example.com ».
- Votre base de données Firebase prend la forme d’un arbre. À la base, on trouve une collection, remplie de documents. Ces documents seront peut-être remplis de sous collections, qui contiendront elles-mêmes des sous-documents, etc. Ces collections et ces documents sont définis, d’abord par des noms uniques qui permettent de les retrouver, mais aussi par des champs. Ces champs sont définis par un nom et contiennent chacun les informations que vous souhaitez stocker et renvoyer.
- Exemple : Pour récupérer l’âge d’un utilisateur dans Firestore :
-
Créer une base de données Firestore
- La première chose à faire pour créer votre base de données Firebase est de créer un projet et de le connecter à votre application Flutter. Si vous ne savez pas comment faire, je vous invite à lire mon article qui traite de comment installer Firebase pour Flutter.
- Une fois cela fait, rendez-vous sur la page principale de votre projet Firebase.
- Dans le tableau de bord de votre projet, cliquez sur « Build » (dans le menu à gauche) > « Firestore Database ».
- Cliquez sur « Créer une base de données ».
- Choix de la région
- Sélectionnez une zone géographique pour héberger votre base de données :
- Astuce : Choisissez une région proche de vos utilisateurs ou de vos services pour améliorer les performances.
- Exemple : europe-west1 (Belgique) pour les utilisateurs en Europe.
- Cliquez sur « Suivant ».
- Choix du mode de sécurité :
-
Ajouter vos données
- Une fois la base de données créée, vous verrez une interface arborescente.
- Une Collection est une catégorie principale (par exemple : Users ou Products).
- Les Documents contiennent les informations associées aux collections.
- Cliquez sur « + Démarrer une collection » pour commencer :
- Donnez un nom à la collection (exemple : Users).
- Cliquez sur « Suivant ».
- Ajoutez un document :
- Vous pouvez laisser Firebase générer un ID unique ou entrer un ID personnalisé.
- Ajoutez des champs avec un nom (par exemple : name, age) et une valeur.
- Cliquez sur « Enregistrer ».
- Ici, nous avons créé une collection appelée Users puis un document appelé 001 qui correspond au nom d’utilisateur d’un utilisateur. Dans ce document, nous avons ajouté une clé email et sa valeur email@email.com.
-
Nous pouvons maintenant passer à la partie codage
-
Importation des dépendances
firebase_core
: nécessaire pour initialiser Firebase dans l’application.cloud_firestore
: permet d’utiliser Firestore, une base de données en temps réel dans Firebase.-
Fonction principale
WidgetsFlutterBinding.ensureInitialized()
: Assure que le framework est correctement initialisé avant l’appel asynchrone.Firebase.initializeApp()
: Initialise Firebase pour que Firestore puisse être utilisé.-
Classe MyApp
- Le widget racine est défini ici. L’application utilise Utilisateurscreen comme page d’accueil.
-
Écran principal (Utilisateurscreen)
- Un widget avec état permet de gérer les interactions dynamiques avec Firestore.
-
État de Utilisateurscreen
-
Variables principales
_firestore
: Instance de Firestore pour effectuer les opérations CRUD.- Les contrôleurs gèrent les valeurs saisies dans les champs de texte.
-
Fonction d’ajout
- Ajoute un nouvel utilisateur à la collection Utilisateurs avec les champs name, age et email.
- Nettoie les champs après l’ajout: nettoyer les contrôleurs garantit que le formulaire est prêt pour la saisie suivante.
-
Fonction de mise à jour
- Met à jour un document existant identifié par son id dans Firestore.
-
Fonction de suppression
- Supprime un document spécifique.
-
Formulaire (Ajout/Modification)
- Affiche un formulaire dans une boîte de dialogue pour ajouter ou modifier un utilisateur.
-
Construction de l’interface utilisateur
StreamBuilder
: Écoute les changements en temps réel dans la collection Utilisateurs.ListTile
: Affiche les données de chaque utilisateur avec des boutons pour modifier et supprimer.-
Utiliser un ID personnalisé dans Firestore (facultatif)
-
Pourquoi utiliser un ID personnalisé ?
- Par défaut, Firestore génère automatiquement un ID unique pour chaque document dans une collection. Cependant, il peut être utile de définir un ID spécifique dans des cas comme :
- Réutiliser un ID déjà connu (par exemple, un identifiant utilisateur ou un code unique).
- Faciliter l’accès au document plus tard en connaissant son ID à l’avance.
-
Structure de la fonction avec ID personnalisé
- Voici la fonction pour ajouter un utilisateur avec un ID défini manuellement :
-
Réalisation
- Définir la collection et le document avec un ID personnalisé :
_firestore.collection('Utilisateurs').doc(customId)
collection('Utilisateurs')
: Sélectionne la collection Utilisateurs.doc(customId)
: Définit ou accède à un document dans cette collection avec l’ID customId.- Si un document avec l’ID customId n’existe pas, Firestore le crée automatiquement.
- Ajouter ou remplacer les données du document :
- La méthode
.set()
ajoute les données ou les remplace si un document avec cet ID existe déjà. - Chaque champ est récupéré à partir des contrôleurs du formulaire.
- Effacer les champs après soumission :
- Cela permet de réinitialiser le formulaire pour une nouvelle saisie.
- Afficher un message dans la console :
- Vous recevez une confirmation dans la console pour indiquer que l’opération s’est terminée avec succès.
-
Comment appeler cette fonction ?
- Pour utiliser cette fonction, vous devez fournir un ID personnalisé en tant que paramètre. Par exemple :
- Exemple dynamique :
- Si vous voulez générer un ID basé sur l’entrée utilisateur (comme le nom), vous pouvez faire :
- Cela générera un ID comme riadh_hajji pour un utilisateur nommé « Riadh HAJJI ».
-
Générer un ID entier dynamique :
-
Utilisation de la méthode
Reduce
: -
Étapes pour générer un ID entier dynamique :
- Récupérer le dernier ID existant dans la collection :
- Parcourez les documents existants dans la collection pour trouver l’ID le plus élevé.
- Incrémenter cet ID :
- Ajoutez 1 à cet ID pour générer le prochain ID.
- Créer le document avec cet ID :
- Utilisez
.doc(customId).set({...})
pour insérer le document. -
Implémentation de la méthode
- Voici la fonction complète pour générer un ID dynamique entier et l’utiliser pour ajouter un utilisateur.
-
Utilisation dans l’application :
- Appelez simplement la fonction
_addUtilisateurAvecIdDynamique()
lors de la soumission du formulaire : -
Utilisation de la méthode avec transaction
-
Etapes de réalisation
- Utilisation d’une collection dédiée au compteur :
- Une collection séparée (par exemple, Counters) est utilisée pour suivre le dernier ID généré.
- Un document spécifique (par exemple, Counters/UtilisateurCounter) contient un champ count qui stocke le dernier ID utilisé.
- Lecture du dernier ID :
- Avant de créer un nouvel utilisateur, la méthode lit le champ count dans le document UtilisateurCounter.
- Cela permet de récupérer le dernier ID généré (le plus récent).
- Incrémentation de l’ID :
- Le nouvel ID est calculé en ajoutant 1 à la valeur lue (dernier ID).
- Mise à jour du compteur :
- La nouvelle valeur de count (le nouvel ID) est immédiatement enregistrée dans le document UtilisateurCounter.
- Cela garantit que le compteur est toujours à jour pour les prochaines transactions.
- Création de l’utilisateur avec l’ID généré :
- Le nouvel utilisateur est ajouté à la collection Utilisateurs avec cet ID.
-
Implémentation de la méthode
- Déclaration de la méthode
Future<void>
: La méthode est asynchrone et ne retourne aucune valeur directe._addUtilisateur
: C’est une méthode privée (préfixée par _) qui ajoute un utilisateur avec un ID unique et incrémental.- Référence au compteur
Collection Counters
: Contient un document appelé UtilisateurCounter qui sert de compteur.counterDoc
: Représente le document qui stocke le dernier ID généré dans le champ count.- Exécution de la transaction
runTransaction
: Garantit que toutes les opérations (lecture et écriture) se font de manière atomique.- Si une autre transaction essaie de modifier le même document au même moment, Firebase s’assure qu’elles ne se chevauchent pas.
- Lecture du dernier ID
transaction.get(counterDoc)
: Récupère le contenu du document UtilisateurCounter.nextId
: Si le document n’existe pas encore (première utilisation), l’ID démarre à 1. Sinon, il est incrémenté en ajoutant 1 à la valeur actuelle du champ count.- Mise à jour du compteur
transaction.set
: Écrit la nouvelle valeur de count dans le document UtilisateurCounter.Nouvelle valeur de count
: Elle correspond au dernier ID généré et est mise à jour immédiatement pour éviter tout conflit.- Création de l’utilisateur
- Formatage de l’ID :
padLeft(3, '0')
: Ajoute des zéros devant l’ID pour avoir une longueur fixe (exemple : 001, 002…).- Utile pour trier ou afficher les IDs de manière uniforme.
- Ajout de l’utilisateur :
collection('Utilisateurs').doc(formattedId)
: Ajoute un document dans la collection Utilisateurs avec l’ID formaté comme clé unique.Données utilisateur
: Les données de l’utilisateur (nom, âge, email) sont ajoutées au document.- Nettoyage des champs
- Vide les contrôleurs de texte après l’ajout de l’utilisateur.
-
Collection : Users
Document : Identifié par userId (par exemple, 12345)
Champ : age
Le chemin complet est :
Users/12345/age
-
Mode test : Toutes les données sont accessibles à tout le monde (utile pour le développement).
(Les règles par défaut expireront après 30 jours pour garantir la sécurité.)
Mode production : Nécessite une authentification pour accéder ou modifier les données.
Cliquez sur « Activer » pour continuer.
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Utilisateurscreen(),
);
}
}
class Utilisateurscreen extends StatefulWidget {
@override
_UtilisateurscreenState createState() => _UtilisateurscreenState();
}
class _UtilisateurscreenState extends State {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
// Controllers for form fields
final TextEditingController _nameController = TextEditingController();
final TextEditingController _ageController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
// Fonction pour ajouter un nouvel Utilisateur
// Note : L'ID du document est généré automatiquement par Firestore
Future _addUtilisateur() async {
await _firestore.collection('Utilisateurs').add({
'name': _nameController.text,
'age': int.parse(_ageController.text),
'email': _emailController.text,
});
// Efface les champs du formulaire après l'ajout
_nameController.clear();
_ageController.clear();
_emailController.clear();
print('Utilisateur added!');
// Confirme dans la console que l'utilisateur a été ajouté
}
// Update an existing Utilisateur
Future _updateUtilisateur(String UtilisateurId) async {
await _firestore.collection('Utilisateurs').doc(UtilisateurId).update({
'name': _nameController.text,
'age': int.parse(_ageController.text),
'email': _emailController.text,
});
_nameController.clear();
_ageController.clear();
_emailController.clear();
print('Utilisateur updated!');
}
// Delete un Utilisateur
Future _deleteUtilisateur(String UtilisateurId) async {
await _firestore.collection('Utilisateurs').doc(UtilisateurId).delete();
print('Utilisateur deleted!');
}
// Show form to add or update un Utilisateur
void _showUtilisateurForm({String? UtilisateurId, Map? UtilisateurData}) {
if (UtilisateurData != null) {
_nameController.text = UtilisateurData['name'];
_ageController.text = UtilisateurData['age'].toString();
_emailController.text = UtilisateurData['email'];
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(UtilisateurId == null ? 'Add Utilisateur' : 'Update Utilisateur'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _nameController,
decoration: InputDecoration(labelText: 'Name'),
),
TextField(
controller: _ageController,
decoration: InputDecoration(labelText: 'Age'),
keyboardType: TextInputType.number,
),
TextField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Cancel'),
),
TextButton(
onPressed: () {
if (UtilisateurId == null) {
_addUtilisateur();
} else {
_updateUtilisateur(UtilisateurId);
}
Navigator.pop(context);
},
child: Text(UtilisateurId == null ? 'Add' : 'Update'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Firestore CRUD Example'),
),
body: StreamBuilder(
stream: _firestore.collection('Utilisateurs').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Center(child: CircularProgressIndicator());
final Utilisateurs = snapshot.data!.docs;
return ListView.builder(
itemCount: Utilisateurs.length,
itemBuilder: (context, index) {
final Utilisateur = Utilisateurs[index];
return ListTile(
title: Text(Utilisateur['name']),
subtitle: Text('Age: ${Utilisateur['age']}, Email: ${Utilisateur['email']}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.edit, color: Colors.blue),
onPressed: () {
_showUtilisateurForm(UtilisateurId: Utilisateur.id, UtilisateurData: Utilisateur.data() as Map);
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
_deleteUtilisateur(Utilisateur.id);
},
),
],
),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_showUtilisateurForm();
},
child: Icon(Icons.add),
),
);
}
}
Future _addUtilisateurAvecId(String customId) async {
await _firestore.collection('Utilisateurs').doc(customId).set({
'name': _nameController.text,
'age': int.parse(_ageController.text),
'email': _emailController.text,
});
_nameController.clear();
_ageController.clear();
_emailController.clear();
print('Utilisateur with custom ID added!');
}
.set({
'name': _nameController.text,
'age': int.parse(_ageController.text),
'email': _emailController.text,
});
_nameController.clear();
_ageController.clear();
_emailController.clear();
print('Utilisateur with custom ID added!');
String customId = "utilisateur_001"; // ID personnalisé
_addUtilisateurAvecId(customId);
String customId = _nameController.text.toLowerCase().replaceAll(' ', '_');
_addUtilisateurAvecId(customId);
Future _addUtilisateurAvecIdDynamique() async {
// Collection de Firestore
final collection = _firestore.collection('Utilisateurs');
// Récupérer tous les documents pour trouver le dernier ID
// Cette requête récupère tous les documents dans la collection Utilisateurs.
QuerySnapshot snapshot = await collection.get();
int nextId = 1; // Par défaut, le premier ID est 1
if (snapshot.docs.isNotEmpty) {
// Trouver le dernier ID maximum parmi les documents
List ids = snapshot.docs.map((doc) => int.tryParse(doc.id) ?? 0).toList();
// Convertit chaque doc.id en entier avec int.tryParse(). Si l'ID n'est pas un entier, il est remplacé par 0.
nextId = (ids.isEmpty ? 0 : ids.reduce((a, b) => a > b ? a : b)) + 1;
// Si la liste est vide (aucun document), l'ID commence à 1.
// Sinon, l'ID est le maximum des IDs existants, augmenté de 1.
}
// Ajouter un document avec l'ID dynamique
await collection.doc(nextId.toString()).set({
'name': _nameController.text,
'age': int.parse(_ageController.text),
'email': _emailController.text,
// Convertit nextId en chaîne (toString()) pour l'utiliser comme ID Firestore.
// Ajoute ou remplace le document.
});
// Effacer les champs du formulaire après ajout
_nameController.clear();
_ageController.clear();
_emailController.clear();
print('Utilisateur added with ID: $nextId');
}
FloatingActionButton(
onPressed: () {
_addUtilisateurAvecIdDynamique();
},
child: Icon(Icons.add),
)
Future _addUtilisateur() async {
DocumentReference counterDoc = _firestore.collection('Counters').doc('UtilisateurCounter');
await _firestore.runTransaction((transaction) async {
DocumentSnapshot snapshot = await transaction.get(counterDoc);
int nextId = 1; // Valeur initiale si le compteur n'existe pas
if (snapshot.exists) {
nextId = snapshot['count'] + 1;
}
transaction.set(counterDoc, {'count': nextId});
String formattedId = nextId.toString().padLeft(3, '0');
transaction.set(
_firestore.collection('Utilisateurs').doc(formattedId),
{
'id': formattedId,
'name': _nameController.text,
'age': int.parse(_ageController.text),
'email': _emailController.text,
},
);
_nameController.clear();
_ageController.clear();
_emailController.clear();