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 получает входные данные через Stream событий (Event) и выдаёт выходные данные через Stream состояний (State). Презентационный слой (Widget) только подписывается на Stream 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-ошибки типа "неизвестный Action" исключены.

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ВысокийНизкий
Отслеживание ActionЧерез тип 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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