Guide d’introduction au widget FutureBuilder dans Flutter


Avatar de Pierre Courtois

Lorsque vous travaillez avec des données asynchrones dans Flutter, il est essentiel d’afficher ces informations de manière fluide et réactive, sans bloquer l’interface utilisateur. Le widget FutureBuilder est l’outil idéal pour gérer des opérations ponctuelles qui renvoient un résultat unique, comme la récupération de données depuis une API ou une base de données. Dans ce…


FutureBuilder Flutter

À 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 :

  1. Initialisation Firebase : Avant d’utiliser Firestore, Firebase est initialisé dans main() avec Firebase.initializeApp().
  2. fetchUserData : Cette fonction asynchrone récupère un document spécifique de la collection users dans Firestore. Si le document est trouvé, il renvoie les données sous forme de Map, sinon il renvoie null en cas d’erreur.
  3. FutureBuilder :
    • future : Le Future correspondant à l’appel de la fonction fetchUserData().
    • builder : Cette fonction génère l’interface utilisateur en fonction de l’état de snapshot :
      • 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.

Avatar de Pierre Courtois