Zurück zur Startseite

Anwendungsfälle in BLoC Flutter: Architektur-Refactoring

Der Artikel beschreibt BLoC-Refactoring in Flutter durch Extrahieren der Geschäftslogik in Anwendungsfälle. Code-Beispiele vorher und nachher werden bereitgestellt, Vorteile für Testen und Skalierbarkeit. Der Ansatz basiert auf Clean Architecture.

BLoC + Anwendungsfälle: Wie vereinfacht man Flutter-Architektur
Advertisement 728x90

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:

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));
    }
  }
}

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:

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()));
    }
  }
}

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:

Google AdInline article slot
  • 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

Advertisement 728x90

Weiterlesen