Что такое BLoC в контексте управления состоянием в Flutter?

«Что такое BLoC в контексте управления состоянием в Flutter?» — вопрос из категории State Management, который задают на 36% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

BLoC (Business Logic Component) — это архитектурный паттерн для управления состоянием в Flutter-приложениях, построенный на реактивных потоках (Streams). Его основная цель — отделить бизнес-логику от слоя представления (UI), что делает код предсказуемым, тестируемым и легко отслеживаемым.

Ключевые компоненты:

  1. События (Events): Входные данные, которые описывают, что произошло (например, нажатие кнопки CounterIncrementPressed).
  2. BLoC: Центральный компонент, который принимает поток событий, обрабатывает их с помощью бизнес-логики и преобразует в поток состояний. Он реализует класс Bloc<Event, State>.
  3. Состояния (States): Выходные данные, которые описывают как должен выглядеть UI в ответ на событие (например, CounterState(count: 5)).

Практический пример — счётчик:

// 1. Определяем события
abstract class CounterEvent {}
class IncrementCounterEvent extends CounterEvent {}

// 2. Определяем состояния
abstract class CounterState {
  final int count;
  const CounterState(this.count);
}
class CounterInitialState extends CounterState {
  CounterInitialState() : super(0);
}
class CounterUpdatedState extends CounterState {
  const CounterUpdatedState(int count) : super(count);
}

// 3. Создаём BLoC
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterInitialState()) {
    // Регистрируем обработчик для события IncrementCounterEvent
    on<IncrementCounterEvent>((event, emit) {
      // Бизнес-логика: увеличиваем счётчик на 1
      emit(CounterUpdatedState(state.count + 1));
    });
  }
}

// 4. Используем в UI с помощью BlocBuilder
BlocBuilder<CounterBloc, CounterState>(
  builder: (context, state) {
    return Column(
      children: [
        Text('Count: ${state.count}'),
        ElevatedButton(
          onPressed: () => context.read<CounterBloc>().add(IncrementCounterEvent()),
          child: const Text('Increment'),
        ),
      ],
    );
  },
)

Почему это важно и когда использовать:

  • Предсказуемость: Состояние изменяется только в ответ на явные события, что упрощает отладку.
  • Тестируемость: Бизнес-логика в BLoC изолирована от UI и легко покрывается unit-тестами.
  • Масштабируемость: Отлично подходит для сложных экранов с множеством взаимосвязанных состояний.
  • Недостаток: По сравнению с более простыми решениями (как Cubit или Provider), требует написания большего количества boilerplate-кода (отдельные классы для событий и состояний).