It would be really helpful if we can produce duplicate states. It will help with the handing of one-time states eg. Navigation, Error messages, Pop-ups, etc..
This was also discussed at #362
How do we control duplicate states then?
listenWhen and buildWhenCan we get around this by not extending Equatable?
Reference to the code snippet
https://github.com/felangel/bloc/blob/93236d623d5d9833ca18bfc843ac832a0956987c/packages/bloc/lib/src/bloc.dart#L242
Thank you for this awesome library :+1:
Hi @karadkar 馃憢
Thanks for opening an issue!
You can produce duplicate states currently. By default if you do not extend Equatable or override == and hashCode each state instance is treated as unique 馃憤
class MyState {}
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(MyState());
@override
Stream<MyState> mapEventToState(MyEvent event) async* {
yield MyState();
await Future.delayed(const Duration(seconds: 3));
yield MyState();
}
}
The above code will trigger two state changes.
I'm guessing your states are overriding == and hashCode either via Equatable or Freezed.
Hope that helps 馃憤
Hi @felangel
Yes, I'm using Equatable in my states. It really helps with the equality checks in tests and other places.
Is there any way we can produce duplicate states while also having a standard == and hashCode implementation?
Or any on-demand configuration which will allow us to do this?
Hi @karadkar 馃憢
The quickest way would be to add a DateTime or some unique id field to your state class.
+1 for this feature request. I don't think it's a good idea to remove Equatable from states.
@karadkar and @SaeedMasoumi why do you feel this is needed? If the state of the bloc has not changed why do you want to notify listeners? Can you please provide a use-case? Thanks! 馃檹
@felangel , I suppose, that the case is defining equals() and hashcode() as unique constraight in DB, when developers try to dispatch entity with updated fields but with the same keySet, they have such problems.
@felangel One more example can be like this:
Assume that users can log in to the app as a guest or as a registered user. In different screens, we need to show specific widgets based on their login state. So, we create a reusable Bloc, called UserBloc, which dispatch the user state (including their tokens, profile and etc). In this case, same state can be dispatch multiple times.
Also:
@vladimirshramov if the fields have been updated shouldn't the values no longer be equal?
@SaeedMasoumi but if the same state is emitted why would you expect the UI to update? The user in that case would not have changed.
@felangel Because of different screens, For example:
I think it's better to have a default implementation for buildWhen or listenWhen which ignores equal states.
@felangel You are right. And I do not add +1 to this request). What i mean is that equals defined not well but it happens. Imho it will be helpful to add one more demo for {one repo + one bloc with complex state + two consumers} case. Some people use your bloc sample for much more complex cases, when repo is needed and reading data using getter is not safe, because it may be not consistent during async operations chain.
class MessageState {
final String message;
final int someData;
MessageState(this.someData, this.message);
}
BlocListener<Bloc, State>(
listener: (context, state) {
if (state is MessageState) {
_showPopup(state);
}
},
);
@felangel
> If the state of the bloc has not changed why do you want to notify listeners? Can you please provide a use-case?
Event -> Some operation then State(Navigate to ScreenB)ScreenA from ScreenB and we may need to repeat these things.test("message state",(){
final state = bloc.state;
expect(state.someData, equals(expectedState.someData));
expect(state.message, equals(expectedState.message));
// TODO: add future members here...
});
== and hashCode implementation, tests can be easytest("message state",(){
expect(bloc.state, equals(expectedState));
});
does copyWith help? If yes how?
class MessageState { final String message; final int someData; MessageState(this.someData, this.message); } BlocListener<Bloc, State>( listener: (context, state) { if (state is MessageState) { _showPopup(state); } }, );@felangel
> If the state of the bloc has not changed why do you want to notify listeners? Can you please provide a use-case?
- We need to show some "Error message" when the user does a wrong action. Users may do that wrong action again so we'll need to show that same message again.
- Another user-case can be of navigation.
On ScreenA,Event->Some operationthenState(Navigate to ScreenB)
Here, if the user comes backScreenAfromScreenBand we may need to repeat these things.- When class don't have an equality check support, the writing tests become harder
test("message state",(){ final state = bloc.state; expect(state.someData, equals(expectedState.someData)); expect(state.message, equals(expectedState.message)); // TODO: add future members here... });
- With proper
==andhashCodeimplementation, tests can be easytest("message state",(){ expect(bloc.state, equals(expectedState)); });
I had the same issue as the user could repeatedly trigger an event, and we would like to show the same error message to the user over and over again. (Such as click some button when the app doesn't have an internet connection), in the end, we added a DateTime property to the failure state as the failure did happen at a different time when the user triggered the action, looks like it works for us.