Problem
When you try to emit a new state that compares equal to the current state of a Bloc, the new state won't be emitted.
This behavior is by design and is discussed here.
When I say "compares equal" I mean that the == operator for the two state objects returns true.
Solution
There are two proper approaches:
- Your state class should NOT extend Equatable. Without Equatable, two objects of the same class with the same fields will NOT compare as equal, and this new state will always be emitted.
- Sometimes you need your state class to extend Equatable. In this case, just add the result of the
identityHashCode(this)
function call to your props
getter implementation:
class NeverEqualState extends Equatable {
@override
List<Object?> get props => [identityHashCode(this)];
}
Note that I use identityHashCode
that works regardless the operator == is overloaded or not. In contrast, hashCode
will not work here.
Warning:
- Do not use random values in the getter implementation
List<Object> get props => [Random().nextDouble()];
. Random variables are random, meaning that with extremely low probability you still might get two equal values in a sequence that will break this workaround. This is extremely unlikely, so it's not possible to reproduce and debug this.
- You can and should include fields in your
get props
implementation, but keep in mind that when all fields compare as equal the objects will also compare as equal.
- Emitting some other state in-between two equal states works but it forces your BlocBuilder to rebuild part of UI and BlocListener to execute some logic. It's just inefficient.
Finally, why would you like to have a state class extend Equatable but still not compare equal? This might be needed when your state class is actually the root of a hierarchy, where some descendants need to implement the == operator properly, and some need to never compare equal. Here is the example:
class BaseMapState extends Equatable {
const BaseMapState();
@override
List<Object?> get props => [];
}
class MapState extends BaseMapState {
final Map<String, Report> reports;
final Report? selectedReport;
final LatLng? selectedPosition;
final bool isLoadingNewReports;
const MapState(
{this.reports = const {},
this.selectedReport,
this.selectedPosition,
this.isLoadingNewReports = false});
@override
List<Object?> get props => [
...reports.values,
selectedReport,
selectedPosition,
isLoadingNewReports
];
}
class ErrorMapState extends BaseMapState {
final String? error;
const ErrorMapState(this.error);
@override
List<Object?> get props => [identityHashCode(this), error];
}
class NeedsAuthMapState extends ErrorMapState {
const NeedsAuthMapState() : super('Authentication required');
}
class NoInternetMapState extends ErrorMapState {
const NoInternetMapState() : super("No Internet connection");
}