Bloc (Business Logic Component) is a state management library built around a strict, event-driven pattern: instead of calling methods directly on your state object, the UI dispatches events, and the Bloc responds by emitting new states. Cubit is a simplified version of the same library that drops the event layer for cases where it’s more overhead than it’s worth.
Setup
dependencies:
flutter_bloc: ^8.1.0
Cubit: The Simpler Starting Point
A Cubit exposes methods that directly emit new state — no separate event classes:
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
}
BlocProvider(
create: (context) => CounterCubit(),
child: BlocBuilder<CounterCubit, int>(
builder: (context, count) {
return ElevatedButton(
onPressed: () => context.read<CounterCubit>().increment(),
child: Text('$count'),
);
},
),
)
emit() is Cubit’s equivalent of Provider’s notifyListeners() or Riverpod’s state assignment — it updates the state and triggers a rebuild of any BlocBuilder watching it.
Bloc: Adding an Event Layer
A full Bloc separates the “what happened” (an event) from the “what changed” (a state), with an explicit mapping between them:
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((event, emit) => emit(state + 1));
}
}
context.read<CounterBloc>().add(Increment());
The UI dispatches an Increment event via add() rather than calling a method directly. This extra layer is what makes Bloc’s logic easy to test in isolation and easy to trace — every state change has a corresponding event you can log or replay — at the cost of writing more classes for the same behavior.
Testing
The bloc_test package lets you assert the exact sequence of states a Bloc or Cubit emits in response to given events, without touching any widget code:
blocTest<CounterBloc, int>(
'emits [1] when Increment is added',
build: () => CounterBloc(),
act: (bloc) => bloc.add(Increment()),
expect: () => [1],
);
Cubit or Bloc?
Start with Cubit — it’s part of the same package, so switching later doesn’t mean a new dependency. Move to full Bloc when you specifically need the traceability of named events, such as for analytics logging, undo/redo, or a team convention that every state change must be triggered by an explicitly named event rather than a method call.