À quoi sert le widget FutureBuilder ?
Comme nous l’avons vu dans l’article précédent, il est courant d’avoir à gérer des données asynchrones. Cela peut être le cas pour récupérer des informations depuis une API, accéder à une base de données ou effectuer d’autres opérations coûteuses en arrière-plan. Mais comment afficher ces données dans l’interface utilisateur de manière fluide tout en tenant compte du délai de récupération ? C’est là qu’intervient le widget FutureBuilder.
Le widget FutureBuilder est un outil puissant de Flutter qui permet d’attendre la complétion d’une tâche asynchrone, en affichant le contenu correspondant à l’état de cette tâche. Il est basé sur le concept de Future, qui représente une donnée disponible à un moment donné… dans le futur.
Le FutureBuilder vous permet de construire des interfaces utilisateurs basées sur l’état du Future
:
- Pendant que le Future est en cours d’exécution (état en attente), vous pouvez afficher un indicateur de chargement (comme un cercle de progression).
- Une fois que le Future se résout avec un résultat (état terminé), vous pouvez afficher les données récupérées.
En cas d’erreur (état échoué), il est également possible d’afficher un message ou une interface d’erreur.
Quand devrais-je utiliser le widget FutureBuilder ?
Le FutureBuilder va vous aider à afficher du contenu lorsque vous avez besoin d’effectuer une opération asynchrone ponctuelle, c’est-à-dire lorsque vous allez récupérer une donnée une seule fois. Vous allez donc pouvoir l’utiliser dans des scénarios où une tâche se termine avec un seul résultat final, comme la récupération de données depuis une API, une requête à une base de données, ou un chargement de fichier. En effet, une fois les données récupérées et affichées, le FutureBuilder
ne pourra plus changer d’état à moins de relancer l’application.
Par exemple, vous pouvez l’utiliser pour :
- Faire un appel unique à une API ou une base de données (ex. : charger le profil d’un utilisateur ou afficher une liste d’articles).
- Charger des fichiers locaux, effectuer un calcul lourd en arrière-plan et en afficher le résultat une fois terminé.
- Afficher un résultat par défaut si dans le cas où vos données ne chargent pas après la récupération initiale.
Le FutureBuilder est donc parfait pour des tâches ponctuelles, où vous attendez un résultat unique. En revanche, vous avez besoin de réagir à des flux continus de données ou des événements qui se produisent à plusieurs reprises, le StreamBuilder, que je présenterai dans le prochain article, sera plus adapté.
Exemple de mise en place d’un FutureBuilder dans Flutter
Voici un exemple simple de l’utilisation du widget FutureBuilder, où je vais récupérer un nom et une adresse mail dans un document Firebase pour les afficher à l’écran :
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:tutoflutter/firebase_options.dart';
void main() async {
// Initialisation de Firebase
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FutureBuilder Firebase Demo',
home: UserScreen(),
);
}
}
class UserScreen extends StatelessWidget {
// Fonction asynchrone pour récupérer les données utilisateur depuis Firebase
Future<Map<String, dynamic>?> fetchUserData() async {
try {
// Récupération du document 'user1' dans la collection 'users'
DocumentSnapshot doc = await FirebaseFirestore.instance
.collection('Users')
.doc('User1')
.get();
return doc.data() as Map<String, dynamic>?;
} catch (e) {
print('Erreur lors de la récupération des données : $e');
return null;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Utilisateur Firebase'),
),
body: Center(
// Utilisation du FutureBuilder pour gérer l'état asynchrone
child: FutureBuilder<Map<String, dynamic>?>(
future:
fetchUserData(), // Appel de la fonction qui récupère les données
builder: (context, snapshot) {
// Pendant le chargement, on affiche un spinner
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
// En cas d'erreur, on affiche un message d'erreur
if (snapshot.hasError) {
return Text('Erreur : ${snapshot.error}');
}
// Si les données sont disponibles, on les affiche
if (snapshot.hasData) {
var userData = snapshot.data!;
print("{userData['name']}");
return Text(
'Nom : ${userData['name']}\nEmail : ${userData['email']}',
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
);
}
// Si aucune donnée n'est retournée, on affiche un message par défaut
return Text('Aucune donnée disponible');
},
),
),
);
}
}
Explication du code :
-
Initialisation Firebase : Avant d’utiliser Firestore, Firebase est initialisé dans
main()
avecFirebase.initializeApp()
. -
fetchUserData
: Cette fonction asynchrone récupère un document spécifique de la collectionusers
dans Firestore. Si le document est trouvé, il renvoie les données sous forme deMap
, sinon il renvoienull
en cas d’erreur. -
FutureBuilder :
-
future
: Le Future correspondant à l’appel de la fonctionfetchUserData()
. -
builder
: Cette fonction génère l’interface utilisateur en fonction de l’état desnapshot
:- En attente (waiting) : Un indicateur de chargement (cercle de progression) est affiché.
- Erreur : Si une erreur survient, elle est affichée à l’utilisateur.
- Données récupérées : Les informations de l’utilisateur (nom et email) sont affichées à l’écran.
-
Pourquoi dois-je donner un type à mon FutureBuilder ?
Si vous regardez le code d’exemple, vous pouvez voir que j’ai défini mon FutureBuilder comme étant de type <Map<String, dynamic>?>. Cela signifie que je vais récupérer des informations qui prennent la forme d’une Map, composée de binome <String, dynamic>. Les clés seront donc de type String
et les valeurs d’un type nom défini puisque je ne sais pas à l’avance ce que je vais récupérer (d’où le type générique dynamic
).
Le point d’interrogation, quant à lui, est là pour signifier que mon Future peut prendre une valeur de nulle, dans le cas où le document n’existe pas, ou la requête échoue.
Ainsi, préciser le type de ressource récupérée dans un FutureBuilder
permet d’assurer une gestion stricte des données attendues et de garantir la stabilité et la clarté du code. Même si ce n’est pas obligatoire, je vous conseille donc de le faire, pour plus de clarté.
Conclusion
Grâce au widget FutureBuilder, vous êtes désormais capable de récupérer une information en ligne de manière ponctuelle et d’aller l’afficher dans votre application Flutter.
Mais comment faire pour afficher du contenu en temps réel de manière dynamique ? Par exemple, comment actualiser une liste de course quand j’ajoute ou supprime des items ? C’est là qu’intervient le widget Streambuilder.
StreamBuilder dans Flutter : Quand et pourquoi l’utiliser ? →