BLoC und Use Cases in Flutter trennen: Clean Architecture live
Das BLoC-Pattern in Flutter wird oft zum God Object: Event-Handler vermischen Service-Aufrufe, Validierungen und State-Emissionen. Das macht Wartung, Tests und Skalierung zum Albtraum. Die Business-Logik in Use Cases auszulagern, stellt die korrekte Schichtung nach Clean-Architecture-Prinzipien wieder her: Presentation (UI und BLoC), Domain (Use Cases, Entities) und Data (Repositories, Services).
Ein Use Case implementiert ein spezifisches Szenario: Er orchestriert Services, behandelt Fehler, wendet Business-Regeln an und bleibt UI-agnostisch. Der BLoC konzentriert sich rein auf den unidirektionalen Datenfluss – Events verarbeiten und States emittieren.
Klassischer BLoC: Wo Logik mit UI verknotet wird
Ein typischer BLoC ruft Services direkt via Dependency Injection auf und führt Side Effects direkt in den Handlern aus. Hier ein vereinfachtes ItemsBloc-Beispiel:
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));
}
}
}
Probleme: Enge Kopplung, schwer testbarer Code und Verletzung des Single-Responsibility-Prinzips (SRP). Der BLoC weiß zu viel über Daten, Netzwerk und Business-Logik.
Refactoring: BLoC als reiner State-Manager
Nach dem Refactoring mappt der BLoC nur noch Events zu States, ohne Abhängigkeiten:
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));
}
}
Der Use Case übernimmt die Orchestrierung:
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()));
}
}
}
Der BLoC schrumpft auf 20 Zeilen und wird vollständig testbar: Events mocken und States überprüfen.
Vorteile von Use Cases als Orchestratoren
Ein Use Case ist mehr als ein Proxy – er kombiniert Daten aus Repositories, wendet Regeln an, protokolliert Aktivitäten und synchronisiert. Er hängt von Abstraktionen (Interfaces) ab, nicht von konkreten Implementierungen.
Wichtige Vorteile:
- Testbarkeit: Services/Repositories mocken für Unit-Tests der Use Cases.
- Skalierbarkeit: Neue Szenarien hinzufügen, ohne den BLoC anzutasten (Open/Closed-Prinzip).
- DI-Flexibilität: Services in Use Cases injizieren; BLoC bleibt unabhängig.
- Lesbarkeit: Business-Szenarien isoliert in eigenen Klassen.
In einem realen Projekt hat dieses Refactoring die Entwicklungszeit um 30 % gekürzt, Bugs reduziert und Tests vereinfacht.
Datenfluss und Abwägungen
UI → Use Case → Services/Repositories → Events an BLoC → UI-State. Der Use Case kennt den BLoC (ein Kompromiss in der Clean Architecture für Einfachheit), aber die UI ist frei von bloc.add-Aufrufen.
BLoC ermöglicht unidirektionalen Datenfluss: vorhersehbares Verhalten und einfache Tests. Use Cases werden unabhängig getestet: Responses simulieren und Aufrufe prüfen.
Wichtige Erkenntnisse
- Schichtung: BLoC als State-Manager, Use Cases für Domain-Logik.
- SRP live: Jede Klasse hat eine Aufgabe.
- Vereinfachte Tests: Pure Functions im BLoC, Mocks in Use Cases.
- Skalierbarkeit: Funktioniert mit Cubit, Riverpod oder anderen State-Managern.
Dieser Ansatz passt zu jedem Flutter-Projekt, wo der BLoC aufgebläht ist. Messen Sie Vorher/Nachher-Metriken: Klassengrößen, Testabdeckung, Feature-Lieferzeit.
— Editorial Team
Noch keine Kommentare.