Sparround

Cubit: emit and the state stream

Cubit extends BlocBase and changes state through method calls. The initial state is passed to the constructor, and methods publish new states with emit().

The core rules:

  • state — synchronous access to the current state.
  • emit(newState) — publishes a new state. It is called only from inside the cubit; changing state from outside is impossible, and that restriction is deliberate.
  • A warning from the official docs: both blocs and cubits ignore duplicate states — when state == nextState, no state change occurs.
  • close() — the end of the cubit's life; BlocProvider calls it automatically.
dart
class WeatherState {
  const WeatherState({
    this.isLoading = false,
    this.weather,
    this.error,
  });

  final bool isLoading;
  final Weather? weather;
  final String? error;

  WeatherState copyWith({bool? isLoading, Weather? weather, String? error}) =>
      WeatherState(
        isLoading: isLoading ?? this.isLoading,
        weather: weather ?? this.weather,
        error: error,
      );
}

class WeatherCubit extends Cubit<WeatherState> {
  WeatherCubit(this._repository) : super(const WeatherState());

  final WeatherRepository _repository;

  Future<void> load(String city) async {
    emit(state.copyWith(isLoading: true, error: null));
    try {
      final weather = await _repository.fetch(city);
      emit(state.copyWith(isLoading: false, weather: weather));
    } catch (error) {
      emit(state.copyWith(isLoading: false, error: '$error'));
    }
  }
}

A cubit doing async work: loading, result and error.

The duplicate-state rule cuts both ways in practice. The benefit: re-emitting the same value does not rebuild the UI needlessly. The trap: if the state class does not implement == properly (no Equatable, so every copyWith is a new object), every emit counts as "new" and the filter never fires; conversely, if you mutate a field in place and emit the same object, == sees no difference and the UI never updates.

CubitBloc
State changes through a method callState changes through an event
Less code: no event classesMore code, but every change has a name
What changed the state is visible at the call site`onTransition` records the whole chain (state → event → state)
Reactive operators (debounce, throttle) are hand-writtenBuilt-in support through `EventTransformer`

📚 Sources and documentation