BLoC (Business Logic Component) — un patron de gestion d'état pour Flutter, présenté par Google en 2018 à la DartConf. BLoC sépare la logique métier de l'interface utilisateur via des flux réactifs (Stream) : l'UI envoie un Event, BLoC le traite et retourne un nouveau State via Stream. Selon pub.dev, le paquet flutter_bloc a recueilli plus de 11 000 likes et est utilisé dans des milliers d'applications Flutter.
Points clés
BLoC (Business Logic Component) — un patron architectural pour Flutter dans lequel la logique métier est extraite dans une classe séparée, isolée de l'UI. BLoC reçoit les données d'entrée via un flux d'événements (Event) et produit les données de sortie via un flux d'états (State). La couche de présentation (Widget) s'abonne uniquement au flux State et rend l'UI, sans jamais exécuter directement la logique métier.
Le concept BLoC est basé sur la programmation réactive et le patron Observer. Chaque composant BLoC est un module séparé avec un contrat clair : un ensemble connu d'Events (ce qui peut arriver) et un ensemble connu de States (ce qui peut être affiché). Un développeur ne peut pas modifier "accidentellement" l'état depuis l'UI — seulement via un Event spécifique. Cela rend le code prévisible et testable.
Selon le sondage Flutter Community 2025, BLoC occupe la deuxième place en popularité parmi les solutions de gestion d'état dans Flutter après Provider. Principaux avantages : typage fort, isolation de la logique, support natif des Stream, écosystème riche d'utilitaires (BlocProvider, BlocListener, BlocSelector).
L'architecture BLoC est construite autour de trois entités : Event (entrée), Bloc (gestionnaire) et State (sortie). Le Widget envoie un Event via la méthode add(). Bloc reçoit l'Event dans la méthode mapEventToState ou on<Event>, exécute la logique métier et émet un nouveau State via yield. Le Widget reçoit le State via un Stream et se reconstruit.
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state + 1;
} else if (event is Decrement) {
yield state - 1;
}
}
}Sécurité des types : Bloc est paramétré avec deux types — Event et State. Le compilateur Dart vérifie que le Widget n'appelle que des Events déclarés et que Bloc ne retourne que des States déclarés. Les erreurs d'exécution comme "Action inconnue" sont éliminées.
Close et Dispose : Bloc implémente l'interface Closeable. Lorsqu'un widget est détruit, Bloc ferme automatiquement le Stream via la méthode close(). Les fuites d'abonnements réactifs sont impossibles — BlocProvider gère le cycle de vie de Bloc, en le liant à une route ou une page.
Cubit est une implémentation simplifiée de Bloc sans Event, introduite dans le paquet flutter_bloc 6.0. Cubit déclare des méthodes directement au lieu de classes Event : increment(), fetchData(). En interne, Cubit utilise le même mécanisme basé sur Stream mais masque la couche Event. Cela réduit le boilerplate de 40 à 50 % pour les scénarios simples.
| Caractéristique | Bloc | Cubit |
|---|---|---|
| Classes Event | Obligatoires | Non nécessaires |
| Boilerplate | Élevé | Faible |
| Suivi des actions | Via le type Event | Nom de méthode uniquement |
| Idéal pour | Scénarios complexes | États simples |
| Analytique | Automatique par Event | Manuelle |
Quand choisir Cubit : état avec 2-3 variantes (loading, loaded, error), formulaires simples, compteurs, états d'UI (ouvert/fermé). Quand choisir Bloc : logique métier complexe avec de multiples actions : traitement de commandes, autorisation, synchronisation de données. Bloc fournit un traçage détaillé de chaque action via Event — chaque appel est enregistré dans BlocObserver.
BlocObserver — un observateur global qui suit tous les Bloc et Cubit dans l'application. Il permet de journaliser Event, State, les erreurs et les transitions. Il suffit de connecter une instance : Bloc.observer = AppBlocObserver(), et tout le traçage d'état de l'application est disponible centralement.
BlocProvider — un InheritedWidget de flutter_bloc qui fournit un Bloc aux widgets enfants. Lors de l'initialisation d'un widget, BlocProvider crée un Bloc, et lors de sa destruction — le ferme automatiquement via close(). BlocProvider peut être placé au niveau du MaterialApp (Bloc global) ou au niveau d'une route spécifique (Bloc local).
BlocProvider(
create: (context) => CounterBloc(),
child: Column(
children: [
BlocBuilder<CounterBloc, int>(
builder: (context, state) => Text('$state'),
),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Text('+'),
),
],
),
)BlocBuilder — un widget qui reconstruit l'UI à chaque nouveau State. BlocListener — pour les effets de bord (traiter un State une fois, sans reconstruire l'UI) : afficher un SnackBar, naviguer vers un autre écran. BlocConsumer — une combinaison de Builder et Listener pour les cas où une reconstruction et un effet de bord sont nécessaires. BlocSelector — pour une reconstruction sélective uniquement lorsqu'un champ spécifique du State change.
MultiBlocProvider — un widget pour les BlocProviders imbriqués sans augmenter les niveaux d'imbrication. Une application Flutter avec 10-15 Blocs utilise MultiBlocProvider au niveau racine pour enregistrer tous les Blocs disponibles pour toute l'application : AuthenticationBloc, CartBloc, SettingsBloc.
BLoC est testé en isolation sans widgets Flutter. Il suffit d'importer le paquet Dart flutter_test et le paquet bloc_test. Le scénario de test : créer un Bloc, ajouter un Event, vérifier le State. blocTest — un utilitaire qui automatise la séquence : build → act → expect.
blocTest<CounterBloc, int>(
'emits [1] when Increment is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(Increment()),
expect: () => [1],
)Mocking : Un Bloc qui dépend d'un dépôt ou d'une API est testé avec des mocks via mocktail. Le dépôt est mocké au niveau d'abstraction, et le Bloc reçoit les dépendances mockées via le constructeur. Hydrated Bloc — une extension pour la persistance/restauration automatique de l'état dans le stockage local. Il est testé avec HydratedBlocStorage et un stockage de fichier temporaire.
Dossiers et fichiers : une structure typique de projet Flutter avec BLoC : bloc/counter_bloc.dart, bloc/counter_event.dart, bloc/counter_state.dart. Pour 30+ écrans, un regroupement par fonctionnalités est recommandé : features/auth/bloc/, features/cart/bloc/. Chaque Bloc est un fichier séparé, chaque Event et State — soit dans des fichiers séparés, soit dans un fichier avec le Bloc.
Performance : BLoC ne crée pas de surcharge pour les Stream vides. BlocBuilder utilise buildWhen pour filtrer les reconstructions — le widget se met à jour uniquement lorsqu'une condition spécifique change. Close garantit que les Blocs inactifs ne consomment pas de mémoire. Selon Flutter DevTools, BLoC ajoute moins de 1 % à la taille du bundle.
Migration depuis Provider : BLoC coexiste facilement avec Provider dans le même projet. Migration progressive : remplacez d'abord les Providers les plus complexes par Bloc, puis le reste. BlocProvider est compatible avec l'arbre Provider : les anciens widgets peuvent utiliser Provider, les nouveaux — BlocProvider, au sein d'une même application.
Questions fréquentes
BLoC utilise Event + Stream pour l'isolation de la logique métier et le typage fort. Provider est une enveloppe autour d'InheritedWidget pour l'injection de dépendances simple et ChangeNotifier. BLoC est meilleur pour les scénarios complexes avec de multiples états, Provider — pour l'état d'UI local. BLoC nécessite plus de boilerplate mais offre une traçabilité complète via les Events.
Hydrated Bloc est une extension du paquet hydrated_bloc qui sauvegarde automatiquement le dernier State dans le stockage local (Hive par défaut). Lors du redémarrage de l'application, Bloc restaure l'état sauvegardé au lieu de l'état initial. Cela résout le problème de persistance sans appels manuels de sauvegarde : connexion, panier, paramètres sont sauvegardés automatiquement entre les sessions.
Une erreur dans BLoC est gérée via try-catch à l'intérieur de mapEventToState ou on<Event>. En cas d'erreur, Bloc retourne un State d'erreur : yield LoadError(error.message). Sur l'UI, BlocListener ou BlocConsumer vérifie le State pour le type d'erreur et affiche un SnackBar ou une boîte de dialogue. BlocObserver journalise globalement toutes les exceptions non gérées.
BLoC est un patron spécifique à Flutter car il utilise Dart Stream et les widgets Flutter. Le concept Event → Bloc → State peut être adapté pour AngularDart et Server-side Dart, mais l'écosystème principal (BlocProvider, BlocBuilder, BlocObserver) est lié à Flutter. Pour React Native, utilisez Redux ou MobX ; pour SwiftUI, utilisez Combine + MVVM.
Cubit — pour les états simples (compteur, toggle, formulaire avec 2-3 champs). Bloc — pour la logique complexe (fil d'actualités, traitement de commandes, autorisation). La règle principale : si vous avez besoin du traçage de chaque action (Event) pour l'analytique ou le débogage — choisissez Bloc. Si les méthodes qui changent l'état sont suffisantes — choisissez Cubit. Les deux patrons peuvent coexister dans le même projet.
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