BLoC — що це таке, Business Logic Component у Flutter

Автор: IT Sectr Опубліковано: 2026-02-19 Час читання: 7 хв

BLoC (Business Logic Component) — патерн керування станом для Flutter, представлений Google у 2018 році на DartConf. BLoC розділяє бізнес-логіку та користувацький інтерфейс через реактивні потоки (Stream): UI надсилає Event, BLoC опрацьовує його та повертає нове State через Stream. За даними pub.dev, пакет flutter_bloc набрав понад 11 тисяч лайків і використовується в тисячах Flutter-застосунків.

Головне

  • Event — вхідний сигнал, що описує дію: натискання кнопки, завантаження даних
  • State — вихідний стан UI: дані завантажено, помилка, завантаження
  • Bloc — основний клас, що приймає Event та повертає State через Stream
  • Cubit — спрощена версія Bloc без Event, що викликає функції безпосередньо
  • BlocProvider — віджет Flutter для впровадження Bloc у дерево віджетів

Що таке BLoC?

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

Архітектура BLoC будується навколо трьох сутностей: Event (вхід), Bloc (обробник) та State (вихід). Widget надсилає Event через метод add(). Bloc приймає Event у методі mapEventToState або on<Event>, виконує бізнес-логіку та видає новий State через yield. Widget отримує State через Stream та перемальовується.

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

Безпека типів: Bloc параметризований двома типами — Event та State. Компілятор Dart перевіряє, що Widget викликає тільки оголошені Event, а Bloc повертає тільки оголошені State. Runtime-помилки типу "невідома дія" виключені.

Close та Dispose: Bloc реалізує інтерфейс Closeable. При знищенні віджета Bloc автоматично закриває Stream через метод close(). Витоку реактивних підписок неможливі — BlocProvider керує життєвим циклом Bloc, пов'язуючи його з роутом або сторінкою.

Bloc та Cubit: порівняння

Cubit — спрощена реалізація Bloc без Event, представлена в пакеті flutter_bloc 6.0. Cubit оголошує методи безпосередньо замість Event-класів: increment(), fetchData(). Всередині Cubit використовує той самий Stream-based механізм, але приховує Event-шар. Це знижує boilerplate на 40-50% для простих сценаріїв.

ХарактеристикаBlocCubit
Event-класиОбов'язковіНе потрібні
BoilerplateВисокийНизький
Відстеження дійЧерез тип EventТільки ім'я методу
Підходить дляСкладних сценаріївПростих станів
АналітикаАвтоматична по EventРучна

Коли вибирати Cubit: стан із 2-3 варіантами (loading, loaded, error), прості форми, лічильники, UI-стани (відкрито/закрито). Коли Bloc: складна бізнес-логіка з безліччю дій: оформлення замовлення, авторизація, синхронізація даних. Bloc дає детальне трасування кожної дії через Event — кожен виклик логується в BlocObserver.

BlocObserver — глобальний спостерігач, що відстежує всі Bloc та Cubit у застосунку. Дозволяє логувати Event, State, помилки та переходи. Досить підключити один екземпляр: Bloc.observer = AppBlocObserver(), і все трасування стану застосунку доступне централізовано.

BlocProvider та BlocBuilder

BlocProvider — InheritedWidget з flutter_bloc, що надає Bloc дочірнім віджетам. При ініціалізації віджета BlocProvider створює Bloc, а при знищенні — автоматично закриває його через close(). BlocProvider може бути розташований на рівні MaterialApp (глобальний Bloc) або на рівні конкретного роута (локальний 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 — віджет, що перебудовує 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

BLoC тестується ізольовано без Flutter-віджетів. Досить імпортувати Dart-пакет flutter_test та пакет bloc_test. Тестовий сценарій: створити Bloc, додати Event, перевірити State. blocTest — утиліта, що автоматизує послідовність: build → act → expect.

Dart
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 та тимчасовим файловим сховищем.

BLoC у продакшені

Папки та файли: типова структура 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 відрізняється від Provider у Flutter?

BLoC використовує Event + Stream для ізоляції бізнес-логіки та строгої типізації. Provider — обгортка над InheritedWidget для простого впровадження залежностей та ChangeNotifier. BLoC краще підходить для складних сценаріїв із багатьма станами, Provider — для локального UI-стану. BLoC потребує більше boilerplate, але дає повну відстежуваність через Event.

Що таке Hydrated Bloc?

Hydrated Bloc — розширення з пакета hydrated_bloc, що автоматично зберігає останнє State у локальному сховищі (за замовчуванням Hive). При перезапуску застосунку Bloc відновлює збережений стан замість початкового. Це вирішує задачу персистентності без ручного виклику збереження: логін, кошик, налаштування зберігаються автоматично між сесіями.

Як обробляти помилки в BLoC?

Помилка в BLoC обробляється через try-catch всередині mapEventToState або on<Event>. При помилці Bloc повертає State помилки: yield LoadError(error.message). На UI BlocListener або BlocConsumer перевіряє State на тип помилки та показує SnackBar або діалог. BlocObserver глобально логує всі необроблені винятки.

Чи можна використовувати BLoC з іншими фреймворками?

BLoC — Flutter-specific патерн, оскільки використовує Dart Stream та віджети Flutter. Концепція Event → Bloc → State може бути адаптована для AngularDart та Server-side Dart, але основна екосистема (BlocProvider, BlocBuilder, BlocObserver) зав'язана на Flutter. Для React Native використовуйте Redux або MobX, для SwiftUI — Combine + MVVM.

Що вибрати: Bloc чи Cubit?

Cubit — для простих станів (лічильник, toggle, форма з 2-3 полями). Bloc — для складної логіки (стрічка новин, оформлення замовлення, авторизація). Основне правило: якщо потрібне трасування кожної дії (Event) для аналітики або налагодження — Bloc. Якщо достатньо методів, що змінюють стан — Cubit. Обидва патерни співіснують в одному проекті.

Підсумки

  • BLoC — патерн керування станом Flutter через Event → Stream → State
  • Event — дія (натискання, завантаження), State — реакція (дані, помилка, завантаження)
  • Cubit — спрощена версія без Event, до 50% менше boilerplate
  • BlocProvider — впровадження Bloc у дерево віджетів з автоматичним close
  • BlocObserver — глобальний моніторинг всіх Bloc та Cubit у застосунку
  • Hydrated Bloc — автоматичний persist стану через Hive
  • blocTest — утиліта для модульного тестування Bloc з ізоляцією від Flutter

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також