With package:bloc, when does a Cubit's emit silently drop a new state, and how does equatable change that decision?
answer
- uses the state's own ==
- equal to current: dropped
- first emit is exempt
- plain class: identity equality
- Equatable: runtimeType plus props
basics
~20 sCubit.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.
solid answer
~40 sIn bloc 9, `emit` returns early when the new state `==` the current one and something has already been emitted, using the state type's own `operator ==`. For `int`, `String`, enums and records that is value equality. For a plain class it is identity: a fresh `WaterState(glasses: 3, dailyLimit: 8)` never equals the current instance, so every emit notifies listeners and rebuilds, even when nothing changed. Extending `Equatable` and listing every field in `props` makes `==` compare the runtime type and the props, so an emit that changes nothing is dropped and tests can compare expected instances. The first-emit exemption lets a Cubit announce a value equal to its initial state once. Leave Equatable out only when identical states back-to-back must notify twice.
code
dart · 16 linesimport 'package:bloc/bloc.dart';
class PlainState {
PlainState(this.glasses);
final int glasses;
}
class PlainCubit extends Cubit<PlainState> {
PlainCubit() : super(PlainState(0));
// Always rebuilds: a new PlainState is never == the current one.
void refresh() => emit(PlainState(state.glasses));
// Never publishes after the first emit: the same instance is == itself.
void touch() => emit(state);
}go deeper
Recall the rule: emit drops a state equal to the current one, and Equatable is how a class state gets value equality from its props.
Explain the exact check, the first-emit exemption, what == means for primitives, records, plain classes and Equatable, and why every field must be in props.
Show judgement on when deduplication hurts, such as a repeated identical error, and how you design states so real repeats still notify.
Weigh a team-wide convention: value equality on every state for fewer rebuilds and simpler tests, against the few states that must repeat, and how reviews catch props omissions.
## The duplicate check inside emit In the **bloc** package (bloc 9.2), every `Cubit` inherits `emit` from `BlocBase`. After confirming the Cubit is not closed, `emit` runs one line that decides whether a state is published at all: ```dart if (state == _state && _emitted) return; ``` - `_state` is the **current** state; `_emitted` starts `false` and flips to `true` after the first successful emit. - When the check returns early, **nothing** happens: no `onChange`, no stream event, no rebuild in any widget listening through flutter_bloc. - The comparison is against the current state only, not history. Emitting A, then B, then A again publishes all three. - The **first emit is exempt** (since bloc 6.0), so a Cubit created with state 0 can still push 0 to listeners once. ## What == means for your state type `emit` does not inspect fields itself; it calls the state's `operator ==`. What that means depends on the type you chose: | State type | `==` compares | Emitting an unchanged value | |---|---|---| | `int`, `String`, `bool` | value | dropped | | enum value | the constant | dropped | | record such as `({int glasses, int limit})` | fields, structurally | dropped | | plain class, no override | identity | a new instance passes, the same instance is dropped | | `Equatable` subclass | runtime type and `props` | dropped | The plain-class row is where teams get surprised in both directions: a Cubit rebuilds on every emit even when nothing changed, and a Cubit that mutates and re-emits the same instance never rebuilds. ## Equatable in a Cubit state The **equatable** package overrides `==` and `hashCode` for you from a `props` list: ```dart import 'package:equatable/equatable.dart'; class WaterState extends Equatable { const WaterState({required this.glasses, required this.dailyLimit}); final int glasses; final int dailyLimit; WaterState copyWith({int? glasses, int? dailyLimit}) => WaterState( glasses: glasses ?? this.glasses, dailyLimit: dailyLimit ?? this.dailyLimit, ); @override List<Object?> get props => [glasses, dailyLimit]; } ``` With that state, a Cubit whose `addGlass()` clamps at the limit and emits `state.copyWith(glasses: state.glasses)` at 8 of 8 glasses produces an equal state, and `emit` drops it — no pointless rebuild. Things to know about Equatable's `==`: - `props` is typed `List<Object?>`, so nullable fields belong in it. - It also requires the same **runtime type**, so two different state classes with identical props are not equal. - `List`, `Set` and `Map` props are compared **element by element**, not by reference. - `Equatable` is annotated `@immutable`, so the analyzer warns about non-final fields in a subclass. - In equatable 3.0 (the pinned source) `EquatableMixin` was removed; `Equatable` is now an `abstract mixin class`, so a class that already extends something writes `with Equatable`. ## When deduplication works against you The bloc FAQ is explicit: do not use Equatable when you want the **same state back-to-back** to trigger twice. The classic case is an error: the user taps save, it fails, they tap again and it fails with the same message. With Equatable the second failure state equals the first, `emit` drops it, and a listener that shows a message never fires again. Options: 1. Emit a different state in between (for example a saving state), so the failure is no longer a repeat. 2. Add a field that changes each time, such as an attempt counter. 3. Leave that state class without value equality. ## A quick decision list 1. Primitive, enum or record state: value equality is already built in. 2. Class state: extend `Equatable` and put **every** distinguishing field in `props`. 3. Need repeated identical notifications: design the state so repeats differ, or skip value equality for it.
- Does a Cubit's emit compare against every earlier state or only the current one?Only the current one: the check is `state == _state`. Emitting A, then B, then A again publishes all three; only an immediately repeated equal state is dropped. Suppressing a value shown several states ago is business logic that belongs in the Cubit's method, not in emit.
- Why does equatable compare runtimeType as well as props?So that different state classes carrying the same fields are not equal. Two subclasses `WaterLoading` and `WaterIdle`, both with empty `props`, would otherwise compare equal, and emitting loading right after idle would be dropped. Equatable's `==` first requires `runtimeType == other.runtimeType`, then compares props.
saying these in an interview costs you the question
- emit always notifies listeners, even when the state did not change
- Without Equatable, a new instance with the same fields is dropped
- Equatable makes states compare by reference instead of by value
- Emitting a value equal to the initial state is always dropped
- Equatable ignores runtimeType, so different subclasses with equal props are equal
- emit drops any state that appeared anywhere in the Cubit's history