BLoC (Business Logic Component) — wzorzec zarządzania stanem dla Flutter, przedstawiony przez Google w 2018 roku na DartConf. BLoC oddziela logikę biznesową od interfejsu użytkownika poprzez reaktywne strumienie (Stream): UI wysyła Event, BLoC go przetwarza i zwraca nowy State przez Stream. Według danych pub.dev, pakiet flutter_bloc zdobył ponad 11 tysięcy polubień i jest używany w tysiącach aplikacji Flutter.
Najważniejsze
BLoC (Business Logic Component) — wzorzec architektoniczny dla Flutter, w którym logika biznesowa jest wyodrębniona do osobnej klasy, izolowanej od UI. BLoC otrzymuje dane wejściowe przez strumień zdarzeń (Event) i emituje dane wyjściowe przez strumień stanów (State). Warstwa prezentacyjna (Widget) tylko subskrybuje strumień State i renderuje UI, nigdy nie wykonując logiki biznesowej bezpośrednio.
Koncepcja BLoC opiera się na programowaniu reaktywnym i wzorcu Observer. Każdy komponent BLoC to osobny moduł z jasno określonym kontraktem: znany jest zbiór Event (co może się zdarzyć) i zbiór State (co może być wyświetlone). Deweloper nie może "przypadkowo" zmienić stanu z UI — tylko przez konkretny Event. To sprawia, że kod jest przewidywalny i testowalny.
Według danych ankiety Flutter Community 2025, BLoC zajmuje drugie miejsce pod względem popularności wśród rozwiązań zarządzania stanem we Flutter po Provider. Główne zalety: ścisła typizacja, izolacja logiki, wbudowane wsparcie Stream, bogaty ekosystem narzędzi (BlocProvider, BlocListener, BlocSelector).
Architektura BLoC opiera się na trzech bytach: Event (wejście), Bloc (przetwarzanie) i State (wyjście). Widget wysyła Event przez metodę add(). Bloc przyjmuje Event w metodzie mapEventToState lub on<Event>, wykonuje logikę biznesową i emituje nowy State przez yield. Widget otrzymuje State przez Stream i przerysowuje się.
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) {
yield state + 1;
} else if (event is Decrement) {
yield state - 1;
}
}
}Bezpieczeństwo typów: Bloc jest sparametryzowany dwoma typami — Event i State. Kompilator Dart sprawdza, czy Widget wywołuje tylko zadeklarowane Event, a Bloc zwraca tylko zadeklarowane State. Błędy wykonawcze typu "nieznana Akcja" są wykluczone.
Close i Dispose: Bloc implementuje interfejs Closeable. Podczas niszczenia widgetu Bloc automatycznie zamyka Stream przez metodę close(). Wycieki subskrypcji reaktywnych są niemożliwe — BlocProvider zarządza cyklem życia Bloc, wiążąc go z routem lub stroną.
Cubit — uproszczona implementacja Bloc bez Event, wprowadzona w pakiecie flutter_bloc 6.0. Cubit deklaruje metody bezpośrednio zamiast klas Event: increment(), fetchData(). Wewnętrznie Cubit używa tego samego mechanizmu Stream, ale ukrywa warstwę Event. Zmniejsza to boilerplate o 40-50% dla prostych scenariuszy.
| Cecha | Bloc | Cubit |
|---|---|---|
| Klasy Event | Wymagane | Niepotrzebne |
| Boilerplate | Wysoki | Niski |
| Śledzenie akcji | Przez typ Event | Tylko nazwa metody |
| Nadaje się do | Złożonych scenariuszy | Prostych stanów |
| Analityka | Automatyczna przez Event | Ręczna |
Kiedy wybierać Cubit: stan z 2-3 wariantami (loading, loaded, error), proste formularze, liczniki, stany UI (otwarty/zamknięty). Kiedy Bloc: złożona logika biznesowa z wieloma działaniami: składanie zamówienia, autoryzacja, synchronizacja danych. Bloc zapewnia szczegółowe śledzenie każdego działania przez Event — każde wywołanie jest logowane w BlocObserver.
BlocObserver — globalny obserwator śledzący wszystkie Bloc i Cubit w aplikacji. Pozwala logować Event, State, błędy i przejścia. Wystarczy podłączyć jedną instancję: Bloc.observer = AppBlocObserver(), a całe śledzenie stanu aplikacji jest dostępne centralnie.
BlocProvider — InheritedWidget z flutter_bloc, udostępniający Bloc widgetom potomnym. Podczas inicjalizacji widgetu BlocProvider tworzy Bloc, a przy zniszczeniu — automatycznie go zamyka przez close(). BlocProvider może być umieszczony na poziomie MaterialApp (globalny Bloc) lub na poziomie konkretnego routu (lokalny Bloc).
BlocProvider(
create: (context) => CounterBloc(),
child: Column(
children: [
BlocBuilder<CounterBloc, int>(
builder: (context, state) => Text('$state'),
),
ElevatedButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: Text('+'),
),
],
),
)BlocBuilder — widget przerysowujący UI przy każdym nowym State. BlocListener — dla efektów ubocznych (jednorazowe przetworzenie State, bez przerysowywania UI): pokazanie SnackBar, nawigacja do innego ekranu. BlocConsumer — kombinacja Builder i Listener dla przypadków, gdy potrzebne jest zarówno przerysowanie, jak i efekt uboczny. BlocSelector — do selektywnego przerysowywania tylko przy zmianie konkretnego pola State.
MultiBlocProvider — widget dla zagnieżdżonych BlocProvider bez zwiększania poziomu zagnieżdżenia. Aplikacja Flutter z 10-15 Bloc używa MultiBlocProvider na poziomie głównym do rejestracji wszystkich Bloc dostępnych w całej aplikacji: AuthenticationBloc, CartBloc, SettingsBloc.
BLoC testuje się izolowanie bez widgetów Flutter. Wystarczy zaimportować pakiet Dart flutter_test i pakiet bloc_test. Scenariusz testowy: utworzyć Bloc, dodać Event, sprawdzić State. blocTest — narzędzie automatyzujące sekwencję: build → act → expect.
blocTest<CounterBloc, int>(
'emits [1] when Increment is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(Increment()),
expect: () => [1],
)Mocking: BLoC zależny od repozytorium lub API testuje się z mockami przez mocktail. Repozytorium jest mockowane na poziomie abstrakcji, Bloc otrzymuje zamockowane zależności przez konstruktor. Hydrated Bloc — rozszerzenie do automatycznego zapisywania/przywracania stanu w lokalnym magazynie. Testuje się z HydratedBlocStorage i tymczasowym magazynem plikowym.
Foldery i pliki: typowa struktura projektu Flutter na BLoC: bloc/counter_bloc.dart, bloc/counter_event.dart, bloc/counter_state.dart. Dla 30+ ekranów zaleca się grupowanie według funkcji: features/auth/bloc/, features/cart/bloc/. Każdy Bloc — osobny plik, każdy Event i State — albo w osobnych plikach, albo w jednym z Bloc.
Wydajność: BLoC nie tworzy narzutu na puste Stream. BlocBuilder używa buildWhen do filtrowania przerysowań — widget aktualizuje się tylko przy zmianie konkretnego warunku. Close gwarantuje, że nieaktywne Bloc nie zużywają pamięci. Według danych Flutter DevTools, BLoC dodaje mniej niż 1% do rozmiaru pakietu.
Migracja z Provider: BLoC łatwo współistnieje z Provider w jednym projekcie. Stopniowa migracja: najpierw zastępuje się najbardziej złożone Provider na Bloc, następnie — pozostałe. BlocProvider jest kompatybilny z drzewem Provider: stare widgety mogą używać Provider, nowe — BlocProvider, w jednej aplikacji.
Często zadawane pytania
BLoC używa Event + Stream do izolacji logiki biznesowej i ścisłej typizacji. Provider — otoczka nad InheritedWidget do prostego wstrzykiwania zależności i ChangeNotifier. BLoC lepiej nadaje się do złożonych scenariuszy z wieloma stanami, Provider — do lokalnego stanu UI. BLoC wymaga więcej boilerplate, ale zapewnia pełną śledzalność przez Event.
Hydrated Bloc — rozszerzenie z pakietu hydrated_bloc, automatycznie zapisujące ostatni State w lokalnym magazynie (domyślnie Hive). Przy ponownym uruchomieniu aplikacji Bloc przywraca zapisany stan zamiast początkowego. Rozwiązuje to problem trwałości bez ręcznego wywoływania zapisu: logowanie, koszyk, ustawienia zapisują się automatycznie między sesjami.
Błąd w BLoC jest obsługiwany przez try-catch wewnątrz mapEventToState lub on<Event>. Przy błędzie Bloc zwraca State błędu: yield LoadError(error.message). Na UI BlocListener lub BlocConsumer sprawdza State pod kątem typu błędu i pokazuje SnackBar lub dialog. BlocObserver globalnie loguje wszystkie nieobsłużone wyjątki.
BLoC — wzorzec specyficzny dla Flutter, ponieważ używa Dart Stream i widgetów Flutter. Koncepcja Event → Bloc → State może być zaadaptowana dla AngularDart i Server-side Dart, ale główny ekosystem (BlocProvider, BlocBuilder, BlocObserver) jest związany z Flutter. Dla React Native używaj Redux lub MobX, dla SwiftUI — Combine + MVVM.
Cubit — dla prostych stanów (licznik, toggle, formularz z 2-3 polami). Bloc — dla złożonej logiki (kanał newsów, składanie zamówienia, autoryzacja). Główna zasada: jeśli potrzebne jest śledzenie każdego działania (Event) dla analityki lub debugowania — Bloc. Jeśli wystarczą metody zmieniające stan — Cubit. Oba wzorce współistnieją w jednym projekcie.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również