Bloc
Bibliothèque de gestion d'état pour Dart et Flutter utilisant le pattern BLoC (Business Logic Component) pour séparer la logique métier de la couche présentation.
Standards fondamentaux
Appliquez ces standards à TOUS les travaux Bloc/Cubit :
- Utilisez
blocTest()depackage:bloc_testpour tous les tests Bloc et Cubit — jamaistest()brut avec assertions manuelles de stream - Utilisez
package:mocktailpour les mocks — jamaispackage:mockito - Pas de dépendances directes bloc-à-bloc — les blocs communiquent via l'UI ou des repositories partagés
- Séparation Page/View — Page fournit le Bloc/Cubit via
BlocProvider, View le consomme viaBlocBuilder/BlocListener - Classes scellées pour les événements et types multi-états — permet l'exhaustivité du pattern matching avec
switchDart 3 - Equatable pour tous les états et événements — hériten de
Equatableet surchargezpropspour l'égalité des valeurs - Logique métier dans Bloc/Cubit uniquement — jamais dans les widgets, pages ou views
- Responsabilité unique — un Bloc/Cubit par préoccupation de fonctionnalité
- Émettez seulement après les vérifications asynchrones — utilisez
emituniquement dans le callback du handler
Cubit vs Bloc
| Aspect | Cubit | Bloc |
|---|---|---|
| API | Fonctions → emit(state) |
Événements → on<Event> → emit(state) |
| Complexité | Basse | Plus élevée |
| Traçabilité | Moins (pas de log d'événements) | Complète (événements + transitions) |
| Quand l'utiliser | État simple, logique pilotée par l'UI | Flux complexes, pilotés par événements, transformations |
| Tests | Appeler des méthodes, affirmer les états | Ajouter des événements, affirmer les états |
Exemple Cubit
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
Exemple Bloc
sealed class CounterEvent extends Equatable {
const CounterEvent();
@override
List<Object> get props => [];
}
final class CounterIncrementPressed extends CounterEvent {}
final class CounterDecrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
on<CounterDecrementPressed>((event, emit) => emit(state - 1));
}
}
Conventions de nommage
Événements
Pattern : BlocSubject + Noun + VerbPastTense
| Nom de classe d'événement | Signification |
|---|---|
TodoListSubscriptionRequested |
S'abonner au stream de liste |
TodoListTodoDeleted |
Supprimer un todo spécifique |
TodoListUndoDeletionRequested |
Annuler la dernière suppression |
LoginFormSubmitted |
Soumettre le formulaire de connexion |
ProfilePageRefreshed |
Rafraîchir la page de profil |
sealed class TodoListEvent extends Equatable {
const TodoListEvent();
@override
List<Object> get props => [];
}
final class TodoListSubscriptionRequested extends TodoListEvent {}
final class TodoListTodoDeleted extends TodoListEvent {
const TodoListTodoDeleted({required this.todo});
final Todo todo;
@override
List<Object> get props => [todo];
}
États
Approche par sous-classe (types d'états multiples)
À utiliser quand chaque état porte des données différentes.
| Nom de classe d'état | Signification |
|---|---|
LoginInitial |
Aucune action entreprise |
LoginInProgress |
Requête de connexion en cours |
LoginSuccess |
Connexion réussie |
LoginFailure |
Connexion échouée |
sealed class LoginState extends Equatable {
const LoginState();
@override
List<Object> get props => [];
}
final class LoginInitial extends LoginState {}
final class LoginInProgress extends LoginState {}
final class LoginSuccess extends LoginState {
const LoginSuccess({required this.user});
final User user;
@override
List<Object> get props => [user];
}
final class LoginFailure extends LoginState {
const LoginFailure({required this.error});
final String error;
@override
List<Object> get props => [error];
}
Approche classe unique (un état, champs multiples)
À utiliser quand tous les états partagent la même structure de données.
| Champ | Type | Objectif |
|---|---|---|
status |
enum |
Statut de chargement actuel |
items |
List<Item> |
Données chargées |
error |
String? |
Message d'erreur en cas d'échec |
enum TodoListStatus { initial, loading, success, failure }
class TodoListState extends Equatable {
const TodoListState({
this.status = TodoListStatus.initial,
this.todos = const [],
this.error,
});
final TodoListStatus status;
final List<Todo> todos;
final String? error;
TodoListState copyWith({
TodoListStatus? status,
List<Todo>? todos,
String? error,
}) {
return TodoListState(
status: status ?? this.status,
todos: todos ?? this.todos,
error: error ?? this.error,
);
}
@override
List<Object?> get props => [status, todos, error];
}
Architecture
| Couche | Contient | Dépend de |
|---|---|---|
| Présentation | Pages, Views, Widgets | Logique métier |
| Logique métier | Blocs, Cubits | Données |
| Données | Repositories, Data Providers | Sources externes |
Couche données
Les repositories abstraient les sources de données et fournissent une API propre aux Blocs/Cubits. Reflétez la structure des dossiers de fonctionnalité sous test/ pour tous les fichiers de test.
Voir references/architecture.md pour l'exemple de repository, la structure des dossiers de fonctionnalité et l'organisation du répertoire de test.
Widgets Flutter
BlocProvider— crée et fournit un Bloc/Cubit au sous-arbreBlocBuilder— reconstruit le widget quand l'état changeBlocListener— exécute les effets secondaires (navigation, snackbar) au changement d'étatBlocConsumer— combineBlocBuilder+BlocListenerBlocSelector— reconstruit seulement quand une propriété sélectionnée change- Utilisez
context.readdans les callbacks (onPressed,onTap),context.watchouBlocBuilderdans les méthodesbuild - Ne jamais utiliser
context.watchen dehors debuild
Voir references/widgets.md pour les tableaux complets des widgets et extensions de contexte, l'exemple du pattern Page/View et l'exemple de BlocListener. Voir references/testing.md pour les paramètres de blocTest(), les exemples de test Cubit/Bloc, le mocking de dépendances et les tests d'intégration de widgets. Voir references/patterns.md pour ajouter des fonctionnalités avec Bloc/Cubit, les opérations asynchrones et les transformateurs d'événements.