BLoC — co to jest, Business Logic Component we Flutter

Autor: IT Sectr Opublikowano: 2026-02-19 Czas czytania: 7 min

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

  • Event — sygnał wejściowy opisujący działanie: kliknięcie przycisku, ładowanie danych
  • State — stan wyjściowy UI: dane załadowane, błąd, ładowanie
  • Bloc — główna klasa przyjmująca Event i zwracająca State przez Stream
  • Cubit — uproszczona wersja Bloc bez Event, wywołująca funkcje bezpośrednio
  • BlocProvider — widget Flutter do wstrzykiwania Bloc do drzewa widgetów

Czym jest BLoC?

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: Event → Bloc → State

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ę.

Dart
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ą.

Bloc i Cubit: porównanie

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.

CechaBlocCubit
Klasy EventWymaganeNiepotrzebne
BoilerplateWysokiNiski
Śledzenie akcjiPrzez typ EventTylko nazwa metody
Nadaje się doZłożonych scenariuszyProstych stanów
AnalitykaAutomatyczna przez EventRę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 i BlocBuilder

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).

Dart
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.

Testowanie BLoC

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.

Dart
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.

BLoC w produkcji

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

Czym BLoC różni się od Provider we Flutter?

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.

Czym jest Hydrated Bloc?

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.

Jak obsługiwać błędy w BLoC?

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.

Czy można używać BLoC z innymi frameworkami?

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.

Co wybrać: Bloc czy Cubit?

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

  • BLoC — wzorzec zarządzania stanem Flutter przez Event → Stream → State
  • Event — działanie (kliknięcie, ładowanie), State — reakcja (dane, błąd, ładowanie)
  • Cubit — uproszczona wersja bez Event, do 50% mniej boilerplate
  • BlocProvider — wstrzykiwanie Bloc do drzewa widgetów z automatycznym close
  • BlocObserver — globalny monitoring wszystkich Bloc i Cubit w aplikacji
  • Hydrated Bloc — automatyczny persist stanu przez Hive
  • blocTest — narzędzie do testowania modułowego Bloc z izolacją od Flutter

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.

Omów projekt

Przeczytaj również