BLoC (Business Logic Component) — патерн керування станом для Flutter, представлений Google у 2018 році на DartConf. BLoC розділяє бізнес-логіку та користувацький інтерфейс через реактивні потоки (Stream): UI надсилає Event, BLoC опрацьовує його та повертає нове State через Stream. За даними pub.dev, пакет flutter_bloc набрав понад 11 тисяч лайків і використовується в тисячах Flutter-застосунків.
Головне
BLoC (Business Logic Component) — архітектурний патерн для Flutter, у якому бізнес-логіка винесена в окремий клас, ізольований від UI. BLoC отримує вхідні дані через потік подій (Event) і видає вихідні дані через потік станів (State). Презентаційний шар (Widget) тільки підписується на потік State та відображає UI, ніколи не виконуючи бізнес-логіку безпосередньо.
Концепція BLoC заснована на реактивному програмуванні та патерні Observer. Кожен компонент BLoC — це окремий модуль із чітким контрактом: відомий набір Event (що може статися) та набір State (що може відображатися). Розробник не може "випадково" змінити стан з UI — тільки через конкретний Event. Це робить код передбачуваним і тестованим.
За даними опитування Flutter Community 2025, BLoC посідає друге місце за популярністю серед рішень керування станом у Flutter після Provider. Основні переваги: строга типізація, ізоляція логіки, вбудована підтримка Stream, багата екосистема утиліт (BlocProvider, BlocListener, BlocSelector).
Архітектура BLoC будується навколо трьох сутностей: Event (вхід), Bloc (обробник) та State (вихід). Widget надсилає Event через метод add(). Bloc приймає Event у методі mapEventToState або on<Event>, виконує бізнес-логіку та видає новий State через yield. Widget отримує State через Stream та перемальовується.
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;
}
}
}Безпека типів: Bloc параметризований двома типами — Event та State. Компілятор Dart перевіряє, що Widget викликає тільки оголошені Event, а Bloc повертає тільки оголошені State. Runtime-помилки типу "невідома дія" виключені.
Close та Dispose: Bloc реалізує інтерфейс Closeable. При знищенні віджета Bloc автоматично закриває Stream через метод close(). Витоку реактивних підписок неможливі — BlocProvider керує життєвим циклом Bloc, пов'язуючи його з роутом або сторінкою.
Cubit — спрощена реалізація Bloc без Event, представлена в пакеті flutter_bloc 6.0. Cubit оголошує методи безпосередньо замість Event-класів: increment(), fetchData(). Всередині Cubit використовує той самий Stream-based механізм, але приховує Event-шар. Це знижує boilerplate на 40-50% для простих сценаріїв.
| Характеристика | Bloc | Cubit |
|---|---|---|
| Event-класи | Обов'язкові | Не потрібні |
| Boilerplate | Високий | Низький |
| Відстеження дій | Через тип Event | Тільки ім'я методу |
| Підходить для | Складних сценаріїв | Простих станів |
| Аналітика | Автоматична по Event | Ручна |
Коли вибирати Cubit: стан із 2-3 варіантами (loading, loaded, error), прості форми, лічильники, UI-стани (відкрито/закрито). Коли Bloc: складна бізнес-логіка з безліччю дій: оформлення замовлення, авторизація, синхронізація даних. Bloc дає детальне трасування кожної дії через Event — кожен виклик логується в BlocObserver.
BlocObserver — глобальний спостерігач, що відстежує всі Bloc та Cubit у застосунку. Дозволяє логувати Event, State, помилки та переходи. Досить підключити один екземпляр: Bloc.observer = AppBlocObserver(), і все трасування стану застосунку доступне централізовано.
BlocProvider — InheritedWidget з flutter_bloc, що надає Bloc дочірнім віджетам. При ініціалізації віджета BlocProvider створює Bloc, а при знищенні — автоматично закриває його через close(). BlocProvider може бути розташований на рівні MaterialApp (глобальний Bloc) або на рівні конкретного роута (локальний 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 — віджет, що перебудовує UI при кожному новому State. BlocListener — для side-ефектів (один раз опрацювати State, без перебудови UI): показати SnackBar, навігувати на інший екран. BlocConsumer — комбінація Builder та Listener для випадків, коли потрібна і перемальовка, і side-ефект. BlocSelector — для селективної перемальовки тільки при зміні конкретного поля State.
MultiBlocProvider — віджет для вкладених BlocProvider без збільшення рівня вкладеності. Flutter-застосунок із 10-15 Bloc використовує MultiBlocProvider на кореневому рівні для реєстрації всіх Bloc, доступних всьому застосунку: AuthenticationBloc, CartBloc, SettingsBloc.
BLoC тестується ізольовано без Flutter-віджетів. Досить імпортувати Dart-пакет flutter_test та пакет bloc_test. Тестовий сценарій: створити Bloc, додати Event, перевірити State. blocTest — утиліта, що автоматизує послідовність: build → act → expect.
blocTest<CounterBloc, int>(
'emits [1] when Increment is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(Increment()),
expect: () => [1],
)Mocking: BLoC, що залежить від репозиторію або API, тестується з моками через mocktail. Репозиторій мокається на рівні абстракції, Bloc отримує замокані залежності через конструктор. Hydrated Bloc — розширення для автоматичного збереження/відновлення стану в локальному сховищі. Тестується з HydratedBlocStorage та тимчасовим файловим сховищем.
Папки та файли: типова структура Flutter-проекту на BLoC: bloc/counter_bloc.dart, bloc/counter_event.dart, bloc/counter_state.dart. Для 30+ екранів рекомендується групування за фічами: features/auth/bloc/, features/cart/bloc/. Кожен Bloc — окремий файл, кожен Event та State — або в окремих файлах, або в одному з Bloc.
Продуктивність: BLoC не створює накладних витрат на порожні Stream. BlocBuilder використовує buildWhen для фільтрації перемальовок — віджет оновлюється тільки при зміні конкретної умови. Close гарантує, що неактивні Bloc не споживають пам'ять. За даними Flutter DevTools, BLoC додає менше 1% до розміру бандла.
Міграція з Provider: BLoC легко співіснує з Provider в одному проекті. Поетапна міграція: спочатку замінюються найскладніші Provider на Bloc, потім — решта. BlocProvider сумісний з Provider-деревом: старі віджети можуть використовувати Provider, нові — BlocProvider, в одному застосунку.
Часті запитання
BLoC використовує Event + Stream для ізоляції бізнес-логіки та строгої типізації. Provider — обгортка над InheritedWidget для простого впровадження залежностей та ChangeNotifier. BLoC краще підходить для складних сценаріїв із багатьма станами, Provider — для локального UI-стану. BLoC потребує більше boilerplate, але дає повну відстежуваність через Event.
Hydrated Bloc — розширення з пакета hydrated_bloc, що автоматично зберігає останнє State у локальному сховищі (за замовчуванням Hive). При перезапуску застосунку Bloc відновлює збережений стан замість початкового. Це вирішує задачу персистентності без ручного виклику збереження: логін, кошик, налаштування зберігаються автоматично між сесіями.
Помилка в BLoC обробляється через try-catch всередині mapEventToState або on<Event>. При помилці Bloc повертає State помилки: yield LoadError(error.message). На UI BlocListener або BlocConsumer перевіряє State на тип помилки та показує SnackBar або діалог. BlocObserver глобально логує всі необроблені винятки.
BLoC — Flutter-specific патерн, оскільки використовує Dart Stream та віджети Flutter. Концепція Event → Bloc → State може бути адаптована для AngularDart та Server-side Dart, але основна екосистема (BlocProvider, BlocBuilder, BlocObserver) зав'язана на Flutter. Для React Native використовуйте Redux або MobX, для SwiftUI — Combine + MVVM.
Cubit — для простих станів (лічильник, toggle, форма з 2-3 полями). Bloc — для складної логіки (стрічка новин, оформлення замовлення, авторизація). Основне правило: якщо потрібне трасування кожної дії (Event) для аналітики або налагодження — Bloc. Якщо достатньо методів, що змінюють стан — Cubit. Обидва патерни співіснують в одному проекті.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.