Bloc: BlocListener no longer observes first state

Created on 13 Jul 2019  路  8Comments  路  Source: felangel/bloc

Describe the bug

370 packages/flutter_bloc/lib/src/bloc_listener.dart line adds a skip(1) operator to the bloc listener. The commit indicates this is intentional so as to only respond to state _updates_. This ended up breaking one of my widget tests where I mock the provided bloc to provide the state update my app needs to react to as the first event in its stream. To fix my test I had to add an arbitrary "1st" event which works, but seems a bit contrived. It seems to me that the first state should count as a state change as the stream changes from no events to one event.

Given #370, this appears to be new expected behavior. However, based on #213, observing the first event was previously intended. Is there a reason why the first state needs to be skipped by the bloc observer?

Expected behavior
The BlocListener observes the first state

question

Most helpful comment

@felangel , I appreciate the response. I gave this some thought and I think this all makes sense to me now. I appreciate the explanation.

All 8 comments

@dustin-graham Hi
Please check this https://github.com/felangel/bloc/issues/368

I just found #368. I get the case where you are trying to only call this function when state changes and the situation we avoid by the skip is confusing the consequence of the listener subscription with actual state updates.

Could we perhaps use BehaviorSubject's hasValue function to determine if we should drop the initial event?

I think there should be a way to keep the fix made in #368 and still include the very first state.

Hey @dustin-graham 馃憢
Thanks for opening an issue!

Regarding your request, by definition BlocListener should be called once for each state change and I don't think the initialState is considered a state change since the bloc defines it's own initialState.

As a result, I think it's unnecessary to have BlocListener be triggered on initialState and it also introduces lots of undesired behavior such as being re-triggered on initialization of widgets even though no state has occurred (as described in #368).

Using the BehaviorSubject's hasValue is also not viable because the BehaviorSubject is seeded with initialState so hasValue will always evaluate to true.

I would love to hear more about why you think this is necessary because instead of

BlocListener(
  bloc: myBloc,
  listener: (context, state) {
    if (state is InitialState) {
      doStuff();
    }
  },
  child: MyChild(),
)

you can just do:

BlocProvider(
  builder: (context) {
    final myBloc = MyBloc();
    doStuff();
    return myBloc;
  },
  child: MyChild(),
)

Let me know if that helps 馃憤

@felangel , I appreciate the response. I gave this some thought and I think this all makes sense to me now. I appreciate the explanation.

The BlocListener is not listening the first state change.

My states:

enum DisplayOptionsState { waiting, success, failure }

My initial state is:

@override
DisplayOptionsState get initialState => DisplayOptionsState.waiting;

My mapEventToState:

  @override
  Stream<DisplayOptionsState> mapEventToState(SetDisplayTypeEvent event) async* {
    try {
      await api.setDisplayState(event.type);
      yield DisplayOptionsState.success;
    } catch (e) {
      yield DisplayOptionsState.failure;
    }
    yield DisplayOptionsState.waiting;
  }

My listener function:

      listener: (context, state) {
        switch (state) {
          case DisplayOptionsState.success:
            _scaffoldKey.currentState.showSnackBar(SnackBar(
              content: Text('Success'),
            ));
            break;
          case DisplayOptionsState.failure:
            _scaffoldKey.currentState.showSnackBar(SnackBar(
              content: Text('Error!'),
              backgroundColor: Colors.red,
            ));
            break;
          default:
            break;
        }
      },

The idea is after a api call, show a snackBar with success or error message. When I dispatch events two times, the listener responds only to the second. Is this the correct behavior?

Hey @danielborges93 馃憢

Can you provide a link to a sample app that illustrates the problem you're having? Alternatively, have you checked out the SnackBar Recipe?

BlocListener should be triggered once for each state change 馃憤

One thing I've noticed is that you're always yielding waiting immediately after either a success or a failure. I'd recommend restructuring your mapEventToState like so:

  @override
  Stream<DisplayOptionsState> mapEventToState(SetDisplayTypeEvent event) async* {
    yield DisplayOptionsState.waiting;
    try {
      await api.setDisplayState(event.type);
      yield DisplayOptionsState.success;
    } catch (e) {
      yield DisplayOptionsState.failure;
    }
  }

Hi @felangel! Thanks for your response!

I have checked the SnackBar Recipe before ;-)
I tried your mapEventToState change suggestion, but the behavior was the same.

The app that I am developping is https://gitlab.com/openlp/openlp-mobile-remote (branch api_base). The widget and bloc are in lib/src/widgets/display_options_dialog.dart and lib/src/bloc/display_options_dialog_bloc.dart.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ricktotec picture ricktotec  路  3Comments

wheel1992 picture wheel1992  路  3Comments

shawnchan2014 picture shawnchan2014  路  3Comments

hivesey picture hivesey  路  3Comments

frankrod picture frankrod  路  3Comments