Bloc: Notifying more than one State will only consume the latest one.

Created on 27 Mar 2019  路  16Comments  路  Source: felangel/bloc

Describe the bug
When notifying 2 consecutive state changes inside mapEventToState only the latest state is propagated to the BlocBuilder.

To Reproduce
Just add the following code and call myBloc.dispathc(TestEvent1()):

  @override
  Stream<MyState> mapEventToState(SectorState currentState, SectorEvent event) async* {
    if (event is TestEvent1) {
      yield TestState1();
      // await Future.delayed(Duration(milliseconds: 250), () {}); // Uncomenting this line will fire both States 
      yield TestState2();
    }
  }

Expected behavior
The BlocBuilder should notify both state changes.

question

Most helpful comment

Hi @joanpuigsanz, Thank you very much for your help. With your sample code, now it is clear how to handle the situation, it helped me out a lot. Thanks Again.

@felangel
First of all, I am really sorry for wasting your valuable time by asking a question before completely reading your awesome tutorials out here.
At the time of asking the question, I was unaware of BlocListener and tried to handle all the states by only using BlocBuilder.

Now my cloud of doubts is moving faster and I am learning things in BLoC. 馃憤
BLoC is really super cool..!! Thanks for BLoC and all these quick support you are providing. 馃憤

All 16 comments

Hi @joanpuigsanz 馃憢

Thanks for opening an issue!

Could you describe what you鈥檇 expect to see and what your use case is?

If you yield two states consecutively there is no time for BlocBuilder to rebuild. By the time the widgets are marked for update the state will have changed again.

Hope that helps! 馃憤

I'm using the Bloc class no notify the widget when the progress dialog needs to be show, for instance:

  @override
  Stream<MyState> mapEventToState(SectorState currentState, SectorEvent event) async* {
    if (event is TestEvent1) {
      yield ShowLoadingState();
      // Do some async tasks
      yield HideLoadingState();
      yield DataResults(hasError);
    }
  }

For now the way I fix it is to dispatch a new internal event that creates yields the HideLoadingState() state. How do you recommend me to improve this issue?

Thanks for the extra details! I'm wondering why do you even need to yield the HideLoadingState event? Isn't it implied that you need to hide the loader when you get the DataResults state?

What if I have two blocs and each one perform some background operations? Shall I make the presenter responsible for hiding the loading?

Then each bloc should yield a loading state, do the async work, and yield a loaded state. The UI should be responsible for showing the loading indicator when the state is loading and removing it when the state is loaded. Let me know if that makes sense.

Closing for now but feel free to comment with additional comments/questions and I'm happy to reopen/continue the conversation. 馃憤

Yes, it make sense but if the two blocks finish at the same time one of the states will get lost because the BlocBuilder will only process the last one. Is this a bug?

I鈥檓 not sure I understand what you mean. BlocBuilder only reacts to changes in a single bloc so if two blocs finish at the same time it shouldn鈥檛 matter because you鈥檇 have two BlocBuilders. Does that help?

I have one bloc which it listens to a document in the Firestore DB. When this document is changed the bloc fires a new DocumentUpdated state with the new document information.

When I change the document in the app later is uploaded and a new Loading state is fired. When document is successfully uploaded the bloc fires a new state DocumentLoaded but as the bloc is also listening to any document change in the server the bloc also fires a the state DocumentUpdated. I would expect the BlocBuilder to receive both states as each state updates a different thing in the UI, but I'm only getting the DocumentUpdated state and not the DocumentLoaded state.

Right now I have some workaround in place to fix this issue but it doesn't fell like it is the correct way to go. I think the BlockBuilder should be able to queue all the states and process all the states, only the last one if more states have been fired at the same time.

Thanks for the info btw.

@joanpuigsanz thanks for the additional details. I'll try to investigate this further over the next few days 馃憤

@joanpuigsanz after investigating this I don't think is a bug in BlocBuilder. This is just how flutter works. If you do the following:

setState(() {
    _state = Loading();
});
setState(() {
    _state = Loaded();
});

You'll see the exact same behavior. I've put together a gist that has nothing to do with Bloc and exhibits the same behavior you are seeing.

If you feel this is a bug with Flutter, I'd recommend opening an issue on Flutter and see what they think.

Thanks! 馃憤

Thanks for the info! very apreciated

Hi @joanpuigsanz

Sorry if it sounds a bit off-topic, but could you please share any hint regarding how you solve this issue?

Right now I have some workaround in place to fix this issue but it doesn't fell like it is the correct way to go.

Hi @felangel
Sorry to trouble you but I am stuck in an application development due to the same issue.
In my case, I have a TextField inside the view (Scaffold) returned from the builder.
Whenever the softinput keyboard is displayed and hidden, the build is calling, and last reported state is again fired.

@override
  Widget build(BuildContext context) {
    return BlocBuilder<CheckinEvent, CheckinState>(
        bloc: _checkinBloc,
        builder: (BuildContext context, CheckinState state) {
          return Container(); /* Its not actual implementation, just dummy */
        }
    );
  }

So to solve this I tried with yield an IdleState just after the actual state as follows:

    yield CheckinStateFailure(error: error.toString());
    yield CheckinStateInitial();

But in this case, only the second state is getting fired.

Is there any solution to trigger 2 states like above mentioned?

Thanks in advance!

Hello @midhunarmid
For the keyboard issue I just place a guard in the bloc to check if it is the same state to check if I need to update the data:

@override
Widget build(BuildContext context) {
   return BlocBuilder<GymEvent, GymState>(
        bloc: _bloc,
        builder: (BuildContext context, state) {
          // Because of the text editor we need to check if the previous state is the same
          if (_previousState != state) {
            // Update my data
          }
          _previousState = state;

          return Container();
        },
      )

Inside the bloc, If I have to fire 2 states, I use the method dispatch inside the bloc, an example would be the following:

class MyBloc extends Bloc<MyEvent, MyState> {

  _someOtherTask() {
   yield LoadingEvent();
  // Some calculations
  dispatch(EventWithState(MyNyewState(data)));
  }

 @override
  Stream<MyState> mapEventToState(event) async*{
  if (event is EventWithState) {
    yield event.state;
  } 
  // ...
 }
}

Never the less if you need to use the EventWithState hack you should check if the design is correct, probably there is a better and cleaner solution.

@midhunarmid are you able to put together a sample app which illustrates the problem you're having? It would be much easier for me to help if I can run the application locally.

In general, the build method of a widget can be called hundreds of times by Flutter. It is out of your control and as a developer, you need to make sure that the build method is a pure function that has no side-effects. It sounds like in your case you have logic that should only execute once per state change; in that case you should move that logic into a BlocListener. You can refer to the Snackbar Recipe for an explanation of when to use BlocBuilder vs BlocListener.

Hope that helps 馃憤

Hi @joanpuigsanz, Thank you very much for your help. With your sample code, now it is clear how to handle the situation, it helped me out a lot. Thanks Again.

@felangel
First of all, I am really sorry for wasting your valuable time by asking a question before completely reading your awesome tutorials out here.
At the time of asking the question, I was unaware of BlocListener and tried to handle all the states by only using BlocBuilder.

Now my cloud of doubts is moving faster and I am learning things in BLoC. 馃憤
BLoC is really super cool..!! Thanks for BLoC and all these quick support you are providing. 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

felangel picture felangel  路  35Comments

fvisticot picture fvisticot  路  31Comments

mariaszek9003 picture mariaszek9003  路  29Comments

pczn0327 picture pczn0327  路  67Comments

basketball-ico picture basketball-ico  路  35Comments