vgv-bloc

Par verygoodopensource · vgv-ai-flutter-plugin

Bonnes pratiques pour la gestion d'état avec Bloc dans Flutter/Dart.

npx skills add https://github.com/verygoodopensource/vgv-ai-flutter-plugin --skill vgv-bloc

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() de package:bloc_test pour tous les tests Bloc et Cubit — jamais test() brut avec assertions manuelles de stream
  • Utilisez package:mocktail pour les mocks — jamais package: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 via BlocBuilder/BlocListener
  • Classes scellées pour les événements et types multi-états — permet l'exhaustivité du pattern matching avec switch Dart 3
  • Equatable pour tous les états et événements — hériten de Equatable et surchargez props pour 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 emit uniquement 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-arbre
  • BlocBuilder — reconstruit le widget quand l'état change
  • BlocListener — exécute les effets secondaires (navigation, snackbar) au changement d'état
  • BlocConsumer — combine BlocBuilder + BlocListener
  • BlocSelector — reconstruit seulement quand une propriété sélectionnée change
  • Utilisez context.read dans les callbacks (onPressed, onTap), context.watch ou BlocBuilder dans les méthodes build
  • Ne jamais utiliser context.watch en dehors de build

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.

Skills similaires