skip to content

BLoC

The bloc family: Cubit and event-driven Bloc classes, flutter_bloc's builder and listener widgets, bloc_test and hydrated_bloc. Interviewers probe why explicit states make a team's code testable.

on this pageshow

explore

questions

27

With package:bloc, what is a Cubit, and how does it hold, change and publish its state?

level: juniorimportance: must knowfreq 74%

answer

  1. extends Cubit<State>, not Bloc
  2. initial value passed to super
  3. emit is @protected
  4. state getter reads synchronously
  5. broadcast stream, no replay

basics

~20 s

A Cubit is a bloc-package state holder: it starts from the value passed to super, changes only when its own methods call emit, exposes the current value through state and publishes each later change on a broadcast stream.

solid answer

~40 s

`Cubit<State>` extends `BlocBase<State>`, the same base `Bloc` uses. Its only constructor takes the initial state, so a subclass writes `: super(0)` and `state` is never unset. Public methods such as `addGlass()` compute the next value and call `emit(next)`; `emit` is annotated `@protected`, so only the Cubit itself should call it. Consumers read the current value synchronously through `state` and subscribe to `stream`, a broadcast stream that delivers only states emitted after `listen` — it never replays the current one. `emit` drops a state equal to the current one (after the first emit), and after `close()` it throws a `StateError`. In Flutter, widgets usually consume a Cubit through flutter_bloc rather than raw `stream.listen`.

code

dart · 25 lines
dart
import 'package:bloc/bloc.dart';

class WaterIntakeCubit extends Cubit<int> {
  WaterIntakeCubit({required int dailyLimit})
      : _dailyLimit = dailyLimit,
        super(0);

  final int _dailyLimit;

  void addGlass() {
    if (state >= _dailyLimit) return;
    emit(state + 1);
  }
}

Future<void> main() async {
  final cubit = WaterIntakeCubit(dailyLimit: 8);
  print(cubit.state); // 0
  final sub = cubit.stream.listen((glasses) => print('stream: $glasses'));
  cubit.addGlass();
  print(cubit.state); // 1, already updated
  await Future<void>.delayed(Duration.zero); // prints stream: 1
  await sub.cancel();
  await cubit.close();
}

go deeper

for a junior

Recall the four pieces: Cubit<State> with the initial value passed to super, methods that call emit, the state getter and the stream getter. Be able to write a small counter Cubit from memory.

for a middle

Explain emit's sequence: closed check, duplicate check, onChange, synchronous assignment, then stream delivery, and why a listener that subscribes late must read state first.

for a senior

Show you manage the lifecycle and API surface: who closes a hand-built Cubit, why emit stays inside the class, and why public methods return void or Future<void>.

for a principal

Frame the Cubit as a team contract: one owner per piece of state, intent-named methods as the only write path, and lint rules that keep the contract enforced across features.

## What a Cubit is In the **bloc** package (bloc 9.2), `Cubit<State>` is the simpler of the two state holders. It is an abstract class that extends `BlocBase<State>` — the same base class `Bloc` extends — and adds nothing but a constructor that takes the **initial state**. Everything else is inherited from `BlocBase`: holding the current value, `emit`, the state stream, error reporting and `close()`. A Cubit has **no events**. Callers invoke ordinary methods on it, such as `addGlass()` or `reset()`, and those methods decide the next state and hand it to `emit`. The type parameter can be anything: an `int` for a counter, an enum, a record, or an immutable class holding several fields. ## Anatomy of a Cubit A counter-and-limit Cubit for a water-intake tracker shows every moving part: ```dart import 'package:bloc/bloc.dart'; class WaterIntakeCubit extends Cubit<int> { WaterIntakeCubit({required int dailyLimit}) : _dailyLimit = dailyLimit, super(0); final int _dailyLimit; void addGlass() { if (state >= _dailyLimit) return; emit(state + 1); } void reset() => emit(0); } ``` - **Initial state** — `super(0)` is mandatory, because `Cubit`'s only constructor requires it. Very old bloc releases let you override an `initialState` getter; bloc 5.0 removed that in favour of passing the value to `super`. - **Methods as the input** — `addGlass()` reads `state`, applies the business rule (never exceed the limit) and emits. - **`emit` as the only write path** — it is annotated `@protected` from `package:meta`, so the analyzer reports `invalid_use_of_protected_member` when code outside the Cubit calls it. It is an annotation, not a language keyword: a lint-level guard rather than a compile error. ## What consumers use | Member | Kind | What it gives you | |---|---|---| | `state` | getter | The current value, read synchronously at any time | | `stream` | getter | A broadcast `Stream<State>` of states emitted after you subscribe | | `isClosed` | getter | Whether `close()` has been called | | `close()` | method | Returns a `Future<void>`; closes the state stream for good | The `stream` is backed by a broadcast `StreamController`, so any number of listeners can subscribe, but **it does not replay the current state**. The bloc documentation says it plainly: only subsequent state changes are received when calling `listen`. Code that needs the current value reads `state` first — which is what flutter_bloc's builder widgets do for their first build before following the stream. ## What happens inside emit `BlocBase.emit` runs a short, fixed sequence: 1. If the internal stream controller is closed, it throws `StateError('Cannot emit new states after calling close')`. 2. If the new state `==` the current state **and** something has been emitted before, it returns without doing anything. 3. It calls `onChange` with a `Change` holding `currentState` and `nextState`. 4. It assigns the new value, so `state` returns it **synchronously**. 5. It adds the value to the stream; listeners receive it asynchronously. Because step 4 precedes step 5, `cubit.state` already holds the new count the moment `addGlass()` returns, even though a `stream.listen` callback has not run yet. ## Lifecycle A Cubit owns a stream controller, so it must be closed when its feature goes away. A Cubit created by flutter_bloc's `BlocProvider(create: ...)` is closed by that provider; one you construct by hand — in a test, a service or `main` — needs an explicit `await cubit.close()`. ## Beginner mistakes to avoid - Mutating a field of the current state and calling `emit(state)`: the same instance is `==` to itself, so the emit is dropped. - Expecting `stream.listen` to deliver the current value. - Calling `emit` from a widget instead of exposing an intent-named method. - Returning results from public Cubit methods: bloc_lint's recommended rule `prefer_void_public_cubit_methods` asks for `void`, `Future<void>` or `FutureOr<void>`, because results should flow out as states.

  • What happens if a Flutter widget calls cubit.emit directly?
    It compiles: `@protected` from `package:meta` is an analyzer annotation, so the analyzer reports `invalid_use_of_protected_member` rather than the compiler refusing. The real damage is bypassing the Cubit's rules — a widget could push 12 glasses past an 8-glass limit. Expose intent-named methods such as `addGlass()` and keep `emit` inside the class.
  • Should a public Cubit method return the new state or a result value?
    The bloc_lint rule `prefer_void_public_cubit_methods`, part of its recommended set, warns on public Cubit methods whose return type is not `void`, `Future<void>` or `FutureOr<void>`. Results belong in emitted states so every consumer sees them; returning `Future<void>` still lets a caller await completion, for example in a pull-to-refresh.

A Cubit is a scoreboard with one operator. Only the operator (the Cubit's methods) changes the score; anyone can glance at the board for the current number (state); fans who start following the updates (stream) hear only the changes made after they tuned in, so to know the score right now they look at the board.

saying these in an interview costs you the question

  • A Cubit's state is null until the first emit
  • Listening to cubit.stream delivers the current state first
  • Cubits take input through add(event), exactly like Bloc
  • emit is private, so a widget calling it cannot compile
  • The state getter lags until stream listeners have run
open as a page

With package:bloc, how does a Bloc turn an event passed to add into one or more new states?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A Bloc receives event objects through add. Each event type has one handler registered with on<E> in the constructor; the handler gets the event and an Emitter and calls it zero or more times, often around an await, to produce new states.

open as a page

With the bloc_test package, how does blocTest drive a Bloc through build, act and expect, and why is the initial state missing from expect?

level: juniorimportance: must knowfreq 50%

basics

~20 s

blocTest builds a fresh bloc, subscribes to its state stream, runs act, closes the bloc and compares every state emitted after that against expect, in order. The constructor's initial state is never pushed to the stream, so it is never recorded.

open as a page

With flutter_bloc, how do BlocBuilder and BlocListener differ, and which one should show a login-failure dialog?

level: juniorimportance: must knowfreq 76%

basics

~20 s

BlocBuilder rebuilds part of the UI from the current state and may run its builder many times, so it must be pure. BlocListener runs a void callback once per state change, never for the initial state, so dialogs, snackbars and navigation belong there.

open as a page

With package:bloc, when is a Cubit enough, and what would make you choose an event-driven Bloc instead?

level: middleimportance: must knowfreq 68%

basics

~20 s

A Cubit is enough when state changes are direct method calls. Choose a Bloc when you need each change traced to the event that caused it, or need to transform incoming events, such as debouncing a search or dropping overlapping requests.

open as a page

With bloc_concurrency, how do the concurrent, sequential, droppable and restartable transformers differ, and when would you pick each?

level: middleimportance: must knowfreq 60%

basics

~20 s

They decide how overlapping events of one handler run: concurrent runs them all at once (the default), sequential queues them in order, droppable ignores new events while one runs, and restartable cancels the running one in favour of the newest.

open as a page

With flutter_bloc, what is the difference between BlocProvider(create:) and BlocProvider.value, and who closes the bloc in each case?

level: middleimportance: must knowfreq 62%

basics

~20 s

BlocProvider(create:) builds the bloc lazily and closes it when the provider leaves the tree. BlocProvider.value exposes an existing instance, for example to a dialog or pushed route, and never closes it; whoever created it does.

open as a page

In the bloc package, what is a BlocObserver for, and how do you register one so it sees every Bloc and Cubit?

level: juniorimportance: should knowfreq 45%

basics

~20 s

A BlocObserver receives lifecycle callbacks - creation, events, changes, transitions, errors, handler completion and closing - from every Bloc and Cubit in one place. Subclass it, call super in each override, and assign it to Bloc.observer in main before any bloc exists.

open as a page

With package:bloc, when does a Cubit's emit silently drop a new state, and how does equatable change that decision?

level: middleimportance: should knowfreq 56%

basics

~20 s

Cubit.emit drops a state that is == to the current one, unless nothing has been emitted yet. A plain class compares by identity, so every new instance gets through; extending Equatable makes value-equal states equal, so no-op emits are dropped.

open as a page

With package:bloc, should a Cubit's state be one class with a status enum or a sealed class hierarchy, and why?

level: middleimportance: should knowfreq 44%

basics

~20 s

Use a sealed hierarchy when states are mutually exclusive and carry different data, for type safety and exhaustive switches; use one class with a status enum when states share data, such as keeping loaded values visible during an error.

open as a page

In the bloc package, what does addError do on a Cubit, and how does it differ from an exception thrown inside a Bloc event handler?

level: middleimportance: should knowfreq 30%

basics

~20 s

addError reports a caught error through the instance's onError to BlocObserver.onError without changing state or throwing. A Bloc handler that throws is reported the same way, then onDone receives the error and the exception is rethrown as an uncaught async error.

open as a page

In the bloc package, in what order do BlocObserver's onEvent, onTransition, onChange and onDone fire for one handled event, and what differs for a Cubit?

level: middleimportance: should knowfreq 35%

basics

~20 s

onEvent fires when add is called; each emit then fires onTransition followed by onChange, both before the state updates; onDone fires when the handler finishes. A Cubit has no events, so its emit fires onChange only.

open as a page

With hydrated_bloc, how do you persist a reading-progress Cubit across app restarts using HydratedCubit, fromJson, toJson and HydratedStorage?

level: middleimportance: should knowfreq 25%

basics

~10 s

Await HydratedStorage.build and assign it to HydratedBloc.storage before runApp, extend HydratedCubit, and implement fromJson and toJson. The saved state is read synchronously in the constructor, and every later change is written back.

open as a page

In a Flutter widget test, how do you replace a real Bloc with bloc_test's MockBloc and drive its states with whenListen?

level: middleimportance: should knowfreq 40%

basics

~10 s

Declare MockCartBloc extends MockBloc<CartEvent, CartState> implements CartBloc, provide it with BlocProvider.value, stub its state, and use whenListen to feed a stream of states while keeping the state getter in sync.

open as a page

With bloc_test, how do you test a Bloc event handler registered with a debounce or restartable event transformer?

level: middleimportance: should knowfreq 25%

basics

~20 s

Pass wait with at least the debounce window so blocTest sleeps before closing the bloc, add several events in quick succession, and expect only the state the surviving event produces; for restartable, expect only the latest request's states.

open as a page

With flutter_bloc, when should you use BlocConsumer rather than a BlocListener wrapping a BlocBuilder, and how does it run its callbacks?

level: middleimportance: should knowfreq 45%

basics

~20 s

BlocConsumer combines a builder and a listener for one bloc, with optional buildWhen and listenWhen, and behaves like a BlocListener around a BlocBuilder. Use it when both reactions belong to the same widget; keep them separate when the listener should span a larger subtree.

open as a page

With flutter_bloc, how do buildWhen and listenWhen filter updates, and what do their previous and current arguments refer to?

level: middleimportance: should knowfreq 55%

basics

~20 s

Both are (previous, current) => bool filters run for every new state; false skips that rebuild or listener call. previous is the last state the widget received from the bloc, not the last one it built or reacted to.

open as a page

With package:bloc, why does an async Cubit method throw a StateError about emitting after close, and how do you prevent it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The Cubit was closed, usually when its screen was disposed, while the method sat at an await; the next emit throws StateError('Cannot emit new states after calling close'). Check isClosed after each await and cancel owned subscriptions in close().

open as a page

A flutter_bloc water-intake Cubit appends a drink to a List inside its Equatable state and emits, but BlocBuilder does not rebuild. What is wrong?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The Cubit mutated the list the current state already holds, so after any earlier emit the new state's props equal the current ones and emit drops it. Emit a state built from a new list, such as [...state.drinks, drink].

open as a page

With package:bloc, why does a handler trigger the assertion 'emit was called after an event handler completed normally', and how do you fix it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The handler's Future completed while work it started was still pending, typically a then callback, an unawaited helper or a Timer, and that work later called emit. Make the handler async and await everything that emits, or return its Future.

open as a page

With package:bloc, when should a Bloc handler follow a repository stream with emit.forEach or emit.onEach, and what must it do with the returned Future?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use emit.forEach when each stream value should become a state, and emit.onEach when each value needs custom handling; both subscribe for the handler's lifetime and must be awaited or returned, or bloc reports pending subscriptions.

open as a page

A blocTest fails although the printed expected and actual CartState lists look identical; what is bloc_test telling you, and how do you fix it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The states compare by identity: CartState does not implement value equality, so equal-looking instances are unequal. bloc_test notices the printed forms match and adds a warning; extend Equatable with every field in props, or assert with matchers.

open as a page

In bloc_test's blocTest, what do seed and skip each do, and why can a seeded test correctly expect an empty list?

level: seniorimportance: should knowfreq 30%

basics

~20 s

seed emits a starting state on the bloc before blocTest subscribes, so it is never recorded; skip drops the first N states recorded after that. Once seeded, emitting a state equal to the current one is ignored, so a no-op event records nothing.

open as a page

With flutter_bloc, how do RepositoryProvider and its dispose callback fit into wiring a feature's repositories and blocs?

level: seniorimportance: should knowfreq 30%

basics

~20 s

RepositoryProvider exposes a non-bloc dependency, such as an AuthRepository, to a subtree; BlocProvider create callbacks read it and pass it to blocs. Since flutter_bloc 9.1 its dispose callback releases the repository when the provider leaves the tree.

open as a page

With flutter_bloc, what does BlocSelector do, and when is it a better fit than BlocBuilder with buildWhen?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

BlocSelector maps each state to one value with its selector and rebuilds only when that value changes, compared with ==. It suits a widget that depends on a single derived value, such as a submit button that needs only whether the form is valid.

open as a page

After a release changes a HydratedCubit's state shape, saved JSON from the old version no longer parses; how does hydrated_bloc 11 react, and how do you migrate safely?

level: seniorimportance: nice to knowfreq 15%

basics

~20 s

If fromJson throws, hydrated_bloc reports it through onError, starts from the initial state and, by default, immediately overwrites the saved data. Migrate by versioning the JSON and reading old shapes in fromJson, with a stable storagePrefix.

open as a page