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