Retour à l'accueil

Cas d'usage dans BLoC Flutter : refactorisation d'architecture

L'article décrit la refactorisation BLoC dans Flutter en extrayant la logique métier vers des cas d'usage. Exemples de code avant et après fournis, avantages pour les tests et la scalabilité. L'approche est basée sur l'architecture propre.

BLoC + cas d'usage : comment simplifier l'architecture Flutter
Advertisement 728x90

Séparer BLoC et cas d'usage en Flutter : Architecture propre en action

Le pattern BLoC de Flutter se transforme souvent en objet Dieu : les gestionnaires d'événements mélangent appels de services, validation et émission d'états. Cela rend la maintenance, les tests et la mise à l'échelle cauchemardesques. Déplacer la logique métier vers des cas d'usage restaure une séparation correcte des couches selon les principes d'architecture propre : présentation (UI et BLoC), domaine (cas d'usage, entités) et données (dépôts, services).

Un cas d'usage implémente un scénario spécifique : il orchestre les services, gère les erreurs, applique les règles métier et reste indépendant de l'UI. Le BLoC se concentre uniquement sur le flux de données unidirectionnel — gestion des événements et émission d'états.

BLoC classique : Où la logique s'enchevêtre avec l'UI

Un BLoC typique appelle directement les services via injection de dépendances, en effectuant des effets secondaires directement dans les gestionnaires. Voici un exemple simplifié de ItemsBloc :

Google AdInline article slot
class ItemsBloc extends Bloc<ItemsEvent, ItemsState> {
  final FetchItemsService _service;

  ItemsBloc(this._service) : super(Initial()) {
    on<FetchItemsEvent>(_onFetchItemsEvent);
  }

  Future<void> _onFetchItemsEvent(FetchItemsEvent event, Emitter<ItemsState> emit) async {
    emit(Loading());
    try {
      final items = await _service.fetchItems();
      emit(Loaded(items: items));
    } catch (error) {
      emit(Error(error: error));
    }
  }
}

Problèmes : couplage fort, code difficile à tester et violations du principe de responsabilité unique (SRP). Le BLoC en sait trop sur les données, le réseau et la logique métier.

Refactoring : BLoC comme gestionnaire d'état pur

Après refactoring, le BLoC ne fait que mapper les événements aux états, sans dépendances :

class ItemsBloc extends Bloc<ItemsEvent, ItemsState> {
  ItemsBloc() : super(Initial()) {
    on<SetLoading>(_onSetLoading);
    on<SetLoaded>(_onSetLoaded);
    on<SetError>(_onSetError);
  }

  Future<void> _onSetLoading(SetLoading event, Emitter<ItemsState> emit) async {
    emit(Loading());
  }

  Future<void> _onSetLoaded(SetLoaded event, Emitter<ItemsState> emit) async {
    emit(Loaded(items: event.items));
  }

  Future<void> _onSetError(SetError event, Emitter<ItemsState> emit) async {
    emit(Error(error: event.error));
  }
}

Le cas d'usage gère l'orchestration :

Google AdInline article slot
class FetchItemsUseCase {
  final ItemsBloc bloc;
  final FetchItemsService service;

  FetchItemsUseCase({required this.bloc, required this.service});

  Future<void> call() async {
    bloc.add(SetLoading());
    try {
      final items = await service.fetchItems();
      bloc.add(SetLoaded(items: items));
    } catch (error) {
      bloc.add(SetError(error: error.toString()));
    }
  }
}

Le BLoC se réduit à 20 lignes et devient totalement testable : simuler des événements et vérifier les états.

Avantages des cas d'usage comme orchestrateurs

Un cas d'usage n'est pas qu'un proxy — il combine des données de dépôts, applique des règles, journalise l'activité et gère la synchronisation. Il dépend d'abstractions (interfaces), pas d'implémentations concrètes.

Avantages clés :

Google AdInline article slot
  • Testabilité : Simuler services/dépôts pour tester les cas d'usage en unité.
  • Évolutivité : Ajouter de nouveaux scénarios sans toucher au BLoC (principe Ouvert/Fermé).
  • Flexibilité DI : Injecter des services dans les cas d'usage ; le BLoC reste indépendant.
  • Lisibilité : Les scénarios métier sont isolés dans des classes dédiées.

Dans un projet réel, ce refactoring a réduit le temps de développement de 30 %, diminué les bugs et simplifié les tests.

Flux de données et compromis

UI → cas d'usage → services/dépôts → événements vers BLoC → état UI. Le cas d'usage connaît le BLoC (compromis d'architecture propre pour la simplicité), mais l'UI est libérée des appels bloc.add.

Le BLoC assure un flux de données unidirectionnel : comportement prévisible et tests faciles. Les cas d'usage sont testés indépendamment : simuler des réponses et vérifier les appels.

Points clés à retenir

  • Séparation des couches : BLoC comme gestionnaire d'état, cas d'usage pour la logique domaine.
  • SRP en action : Chaque classe a une seule responsabilité.
  • Tests simplifiés : Fonctions pures dans le BLoC, mocks dans les cas d'usage.
  • Évolutivité : Compatible avec Cubit, Riverpod ou autres gestionnaires d'état.

Cette approche convient à tout projet Flutter où le BLoC gonfle. Mesurez les métriques avant/après : tailles de classes, couverture de tests, temps de livraison de fonctionnalités.

— Editorial Team

Advertisement 728x90

Lire ensuite