## Oddělení BLoC a use-cases ve Flutteru: čistá architektura v praxi
BLoC ve Flutteru se často mění v god object: obslužné rutiny událostí míchají volání služeb, validaci a emisi stavů. To ztěžuje údržbu, testování a škálování. Vysunutí obchodní logiky do use-cases obnovuje oddělení vrstev podle principů čisté architektury: presentation (UI a BLoC), domain (use-cases, entity) a data (repositoria, služby).
Use-case implementuje konkrétní scénář: orchestruje služby, zpracovává chyby, aplikuje obchodní pravidla, bez závislosti na UI. BLoC se soustředí na unidirectional flow – přijímá události a emituje stavy.
Klasický BLoC: kde se logika mísí s UI
Typický BLoC volá služby přímo přes DI a provádí side-effects v obslužných rutinách. Příklad zjednodušeného ItemsBloc:
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émy: vysoká propojenost, složité testování, porušení SRP. BLoC zná data, síť a obchodní logiku.
Refaktoring: BLoC jako čistý správce stavů
Po refaktoringu obsahuje BLoC pouze mapování událostí na stavy, bez závislostí:
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));
}
}
Use-case přebírá orchestraci:
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()));
}
}
}
BLoC se zkrátí na 20 řádků, stane se testovatelným: mock událostí a kontrola stavů.
Výhody use-cases jako orchestrátora
Use-case není jen proxy – spojuje data z repozitářů, aplikuje pravidla, loguje, synchronizuje. Závisí na abstrakcích (interfacech), ne na implementacích.
Klíčové výhody:
- Testovatelnost: mock služeb/repositorií pro unit testy use-case.
- Škálovatelnost: nové scénáře se přidávají bez změn BLoC (Open/Closed).
- Flexibilita DI: služby se injektují do use-case, BLoC je nezávislý.
- Čitelnost: obchodní scénáře jsou izolované v samostatných třídách.
V reálném projektu refaktoring zkrátil čas vývoje o 30 %, snížil chyby, zjednodušil testy.
Tok dat a kompromisy
UI → use-case → služby/repositoria → události do BLoC → stav UI. Use-case zná BLoC (kompromis čisté architektury pro jednoduchost), ale UI se očistí od bloc.add.
BLoC – unidirectional data flow: předvídatelné chování, snadné testy. Use-cases se testují odděleně: simulace odpovědí, kontrola volání.
Co je důležité
- Oddělení vrstev: BLoC – správce stavů, use-cases – doménová logika.
- SRP v praxi: každá třída má jednu odpovědnost.
- Zjednodušené testování: čisté funkce v BLoC, mocky v use-cases.
- Škálovatelnost: přístup funguje s Cubit, Riverpod nebo jinými správci stavů.
Přístup je použitelný pro jakýkoli Flutter projekt, kde BLoC roste. Změřte metriky před/po: velikost tříd, pokrytí testů, čas na funkce.
— Editorial Team
Zatím žádné komentáře.