StreamBuilder est un widget Flutter qui reconstruit automatiquement l'interface lors de la réception de nouvelles données d'un flux asynchrone. Contrairement à FutureBuilder, qui travaille avec un résultat unique, StreamBuilder prend en charge les mises à jour continues de l'UI tout au long du cycle de vie du Stream. Selon la documentation officielle de Flutter (2026), StreamBuilder est utilisé dans les applications en temps réel : chats, fils d'actualités, surveillance de capteurs et tickers financiers. C'est un outil clé de la programmation réactive, où l'UI reflète l'état des données sans appels manuels à setState.
Points clés
StreamBuilder est un widget du package Flutter SDK qui s'abonne à un Stream et reconstruit son élément enfant à chaque nouvel événement du flux. StreamBuilder accepte un objet Stream et retourne un widget basé sur le dernier snapshot reçu du flux.
Dans l'architecture Flutter, StreamBuilder appartient au groupe des widgets Builder qui séparent la construction de l'UI de l'état des données. Contrairement à StatefulWidget, où changer l'état nécessite un appel explicite à setState, StreamBuilder réagit aux événements asynchrones automatiquement, simplifiant le code et réduisant le risque d'erreurs de synchronisation.
Contrairement à FutureBuilder, qui traite une seule valeur asynchrone, StreamBuilder est conçu pour les flux de données continus. FutureBuilder se termine après avoir reçu le premier résultat, tandis que StreamBuilder continue d'écouter le flux et de mettre à jour l'UI à chaque nouvel événement.
StreamBuilder est utilisé dans tous les scénarios où les données arrivent en continu : connexions WebSocket, callbacks de capteurs, notifications Firebase, files d'événements Bluetooth et diffusion de l'état de l'application via BLoC. Selon l'analyse des projets Flutter sur GitHub (2025), StreamBuilder fait partie des trois widgets Builder les plus utilisés avec FutureBuilder et LayoutBuilder.
Conclusion : utilisez StreamBuilder partout où l'UI doit refléter des données en changement continu, en évitant la gestion manuelle de l'état via StatefulWidget.
StreamBuilder s'abonne à un Stream au moment de la construction et se désabonne lorsque le widget est détruit. Chaque fois que le Stream émet un événement, StreamBuilder reçoit un nouvel AsyncSnapshot et appelle la fonction builder pour reconstruire l'UI.
Le processus se compose de trois étapes. Première : StreamBuilder crée un abonnement au Stream passé via la méthode stream.listen. Deuxième : à chaque événement, StreamBuilder met à jour l'AsyncSnapshot interne et marque le widget comme sale pour reconstruction. Troisième : le framework appelle la fonction builder avec le nouveau snapshot, et l'UI affiche les données actuelles.
Important : StreamBuilder utilise StreamSubscription en interne. Si le Stream est passé directement, StreamBuilder s'abonne une fois lors de l'initialisation. Si le Stream change (par exemple, lors d'une reconstruction du parent), StreamBuilder se désabonne de l'ancien flux et s'abonne au nouveau. Ce comportement est contrôlé par les paramètres initialData et buildWhen, qui permettent d'optimiser le nombre de reconstructions.
Conclusion : comprendre le cycle de vie de l'abonnement est la base d'une utilisation correcte de StreamBuilder. Une mauvaise gestion des flux entraîne des fuites mémoire ou des données obsolètes dans l'UI.
La propriété connectionState de l'objet AsyncSnapshot détermine à quelle étape du traitement du flux se trouve StreamBuilder. Il y a quatre états : none, waiting, active, done.
None est l'état initial lorsque le Stream n'a pas encore commencé à transmettre des données. Dans cet état, snapshot.connectionState est égal à ConnectionState.none et snapshot.data est null. Généralement, un placeholder ou un indicateur d'attente est affiché dans cet état. Si le Stream ne fournit pas de données initiales, StreamBuilder commence dans cet état.
Waiting est l'état d'attente de données d'un flux asynchrone. Le Stream est actif, mais les données ne sont pas encore arrivées. Cet état se produit, par exemple, lors du chargement de données depuis le réseau ou de l'ouverture d'une connexion longue durée. Dans cet état, il est courant d'afficher un CircularProgressIndicator ou un squelette de chargement.
Active — le flux émet des données et l'UI affiche les informations actuelles. Dans cet état, snapshot.hasData est true et snapshot.data contient la dernière valeur du flux. Si le flux est un Broadcast Stream, l'état actif peut coexister avec l'attente de nouvelles données.
Done — le flux est terminé, aucune nouvelle donnée n'arrivera. Snapshot.data contient la dernière valeur transmise avant la fermeture du flux. Si le flux s'est terminé avec succès, snapshot.hasError est false. Cet état est utilisé pour afficher le résultat final : un message comme « Chargement terminé » ou une transition vers l'écran suivant.
Conclusion : lors de la construction de l'UI via StreamBuilder, les quatre états doivent être traités pour que l'interface affiche correctement le chargement, les données, les erreurs et la fin.
StreamController est une classe du package dart:async qui crée et gère un Stream. StreamController permet d'ajouter des données, de traiter les erreurs et de fermer le flux, en contrôlant son cycle de vie.
StreamController existe en deux types : single-subscription (un abonné) et broadcast (plusieurs abonnés). Un contrôleur single-subscription n'accepte qu'un seul auditeur à la fois — un deuxième abonnement lèvera une exception. Un contrôleur broadcast permet à plusieurs StreamBuilder d'écouter le même flux simultanément, ce qui est utile pour BLoC et l'état partagé de l'application.
Lors de la création d'un StreamController via StreamController<T>.broadcast(), les données ajoutées avant le premier abonnement ne sont pas rejouées aux nouveaux abonnés. Pour obtenir la dernière valeur lors de la connexion, utilisez BehaviourSubject du package rxdart, qui met en cache le dernier événement.
Après avoir terminé le travail avec le contrôleur, il faut appeler controller.close(). Ne pas appeler close entraîne des fuites de ressources : le flux reste ouvert, les abonnés restent en mémoire et le GC ne libère pas les objets associés.
Conclusion : utilisez StreamController avec une gestion explicite du cycle de vie. Pour les flux single-subscription, utilisez le contrôleur standard ; pour l'état partagé, utilisez un contrôleur broadcast ou BehaviourSubject.
Exemple 1 montre un minuteur de compte à rebours utilisant StreamController et StreamBuilder.
import 'dart:async';
class TimerWidget extends StatefulWidget {
const TimerWidget({super.key});
final StreamController<int> controller = StreamController<int>();
void startTimer() {
int count = 0;
Timer.periodic(Duration(seconds: 1), (timer) {
controller.sink.add(count++);
if (count > 10) {
controller.close();
timer.cancel();
}
});
}
}
Dans l'exemple, un contrôleur est créé pour générer des nombres de 0 à 10 à intervalles d'une seconde. Après avoir atteint 10, close est appelé et le flux se termine. StreamBuilder, abonné au flux de ce contrôleur, affichera chaque nouvelle valeur.
Exemple 2 — utilisation de StreamBuilder avec un Broadcast Stream pour afficher des données de sources multiples.
final StreamController<String> broadcastController =
StreamController<String>.broadcast();
StreamBuilder<String>(
stream: broadcastController.stream,
initialData: 'Waiting for data...',
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return Text('Erreur : ${snapshot.error}');
}
return Text('Données : ${snapshot.data}');
},
)
Le deuxième exemple montre le traitement de tous les états : initialData pour l'affichage initial, waiting pour l'indicateur de chargement, hasError pour les erreurs et data pour le résultat réussi. Ce modèle est la norme pour le code de production avec StreamBuilder.
Conclusion : utilisez initialData pour éviter un écran vide au premier moment et traitez toujours hasError pour afficher correctement les erreurs à l'utilisateur.
Erreur 1 : création d'un nouveau Stream à chaque reconstruction du parent. Si le Stream est passé via une expression qui crée un nouvel objet à chaque construction, StreamBuilder se désabonne de l'ancien et s'abonne au nouveau flux, provoquant une boucle infinie de reconstructions. Solution : utilisez une variable remembered ou un StatefulWidget avec un Stream fixe.
Erreur 2 : absence de traitement des erreurs. Un Stream peut émettre des erreurs via controller.sink.addError, et si le builder ne vérifie pas snapshot.hasError, l'utilisateur voit un écran vide ou un chargement infini. Solution : vérifiez toujours hasError et affichez un message clair.
Erreur 3 : fuites mémoire dues à un StreamController non fermé. Si le contrôleur n'est pas fermé dans dispose, le flux continue d'exister et le GC ne libère pas la mémoire. Solution : appelez controller.close() dans dispose et écoutez l'événement done pour les actions finales.
Erreur 4 : utilisation de StreamBuilder avec une fonction builder lente. Comme le builder est appelé à chaque événement du flux, des calculs lourds à l'intérieur entraînent des chutes d'images. Solution : déplacez les calculs dans un isolate séparé ou utilisez Stream.map pour la transformation des données.
Conclusion : StreamBuilder est un outil puissant mais exigeant. Surveillez le cycle de vie du Stream, traitez les erreurs et évitez les opérations lourdes dans le builder.
Foire aux questions
FutureBuilder est conçu pour un résultat asynchrone unique : il s'abonne à un Future, reçoit une valeur et se termine. StreamBuilder s'abonne à un Stream, qui peut émettre plusieurs valeurs dans le temps, et reconstruit l'UI à chaque nouvel événement.
AsyncSnapshot est un objet immuable qui contient l'état actuel de l'abonnement (connectionState), la dernière valeur reçue (data) et un objet d'erreur (error) si le flux a émis une exception.
Les erreurs sont traitées via les propriétés snapshot.hasError et snapshot.error dans la fonction builder. Si le flux émet une erreur via sink.addError, AsyncSnapshot reçoit l'erreur, et le builder doit afficher un message approprié ou une UI de secours.
Oui, si le Stream est broadcast (créé via StreamController.broadcast). Un Stream single-subscription n'autorise qu'un seul abonné. Pour partager un flux entre plusieurs widgets, utilisez un contrôleur broadcast ou le package rxdart avec BehaviourSubject.
Utilisez le paramètre buildWhen pour filtrer les événements qui doivent déclencher des reconstructions de l'UI. Appliquez également Stream.transformer ou Stream.where pour filtrer les données avant de les passer à StreamBuilder.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi