Bloc: [Bug?] Listener called when being rebuilt, even though no state changes

Created on 22 Jun 2019  Β·  17Comments  Β·  Source: felangel/bloc

Describe the bug
I have 2 pages with BlocListener in each of them. The Blocs them selves are kept in the
I have a BlocListener on my widget

To Reproduce
Create an app widget with blocs in the state.
Create 2 pages that you can navigate between them
In each page have a BlocListener widget on a state. Make sure the state doesn't change when navigating.

The 'listener' callback will be called every time the BlocListener is rebuilt, even though the state is the same.

Expected behavior
The listener callback in BlocListener should only be called once when the state changes, regardless if the BlocListener was just built or not.

*Logs *

$ flutter analyze
Analyzing my-app...
No issues found! (ran in 5.5s)

Paste the output of running flutter doctor -v here.

[√] Flutter (Channel dev, v1.7.4, on Microsoft Windows [Version 10.0.18362.175], locale en-US)
    β€’ Flutter version 1.7.4 at C:\Flutter
    β€’ Framework revision dfecafa4ab (7 days ago), 2019-06-14 09:46:23 -0700
    β€’ Engine revision 2589785b5c
    β€’ Dart version 2.4.0

[√] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
    β€’ Android SDK at C:\Android\Sdk
    β€’ Android NDK location not configured (optional; useful for native profiling support)
    β€’ Platform android-28, build-tools 28.0.3
    β€’ ANDROID_HOME = C:\Android\Sdk
    β€’ Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
    β€’ Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01)
    β€’ All Android licenses accepted.

[√] Visual Studio - develop for Windows (Visual Studio Community 2017 15.9.10)
    β€’ Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2017\Community
    β€’ Visual Studio Community 2017 version 15.9.28307.557

[√] Android Studio (version 3.4)
    β€’ Android Studio at C:\Program Files\Android\Android Studio
    β€’ Flutter plugin version 36.1.1
    β€’ Dart plugin version 183.6270
    β€’ Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1343-b01)

[√] IntelliJ IDEA Community Edition (version 2019.1)
    β€’ IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2018.1.2
    β€’ Flutter plugin version 36.0.4
    β€’ Dart plugin version 191.7830

[√] Connected device (1 available)
    β€’ Android SDK built for x86 β€’ emulator-5554 β€’ android-x86 β€’ Android 9 (API 28) (emulator)

β€’ No issues found!
bug

Most helpful comment

@felangel thank you :+1: Keep up the awesome work on your packages!

All 17 comments

Fixed by #370 and released in flutter_bloc v0.18.2 πŸŽ‰

Hello @felangel. First of all, thank you for this awesome package!

Regarding this issue. You say in the readme file that: listener is only called once for each state change (including initialState) unlike builder in BlocBuilder and is a void function. I think that with this change the listener is not triggered for the initialState. Can you confirm this?

Hey @biklas7 πŸ‘‹

Yeah you're totally right! I need to update the documentation to say listener is only called once for each state change (not including initialState) unlike builder in BlocBuilder and is a void function. Thanks for pointing this out πŸ’― (fixed in https://github.com/felangel/bloc/commit/bedd754845c9e9ff6fc2a3cdba6d31344c470156)

Maybe I shouldn't put this here but in a project that I'm working on, I was relying on the listener to be called for the initialState to dispatch an event. Since this is not possible, what do you suggest us to do? Maybe convert to a StatefulWidget and dispatch the event in the initState? I don't think this is the best solution :thinking:

@biklas7 you can just dispatch the event right when the bloc is created like so:

BlocProvider(
  builder: (context) => MyBloc()..dispatch(MyBlocEvent()),
  child: Container(),
)

@felangel thank you :+1: Keep up the awesome work on your packages!

@felangel can you provide some guidance around when to use BlocListener vs BlocBuilder? If "BlocBuilder analogous to StreamBuilder", what is BlocListener analogous to?
Thx

@bobwiller BlocBuilder should only be used to return different widgets based on the bloc state. For things like navigation, showing alerts, showing SnackBars, etc ... you should use BlocListener. This is because BlocBuilder can be called many times by the flutter framework whereas BlocListener is only called once per state change. BlocListener is analogous to manually listening to changes in the bloc state and disposing the subscription.

Check out the SnackBar Recipe for more detail πŸ‘

@felangel thx - i feel like the warning at the bottom of that recipe ("We should NEVER....") should be in the BlocBuilder documentation and/or have a reference to that recipe in the BlocListener doc page!

One last question: I feel like you moved to a pattern where you do:
bloc: BlocProvider.of<DataBloc>(context)

...when you need a reference to the Bloc. So in that recipe, you make that call twice - once in the Listener and again in the Builder vs. getting the reference in the constructor like this:
DataBloc _dataBloc = BlocProvider.of<DataBloc>(context)

and just reusing the reference when you need it. I assume there is a reason for this?

Good point! I’ll make that change πŸ‘

You could get the instance in the build method and reuse the reference in both places. You can’t use BlocProvider.of(context) in the constructor because you need to have access to the BuildContext.

The lookup is O(1) so it shouldn’t have any performance impact but I will make that change as well. πŸ‘

I think the bug was reintroduced. I just built an app with BlocListener for my navigation. After I experienced some strange behaviour I put a print(state) statement inside my listener function and the output was something like this:

flutter: AuthInProgress
flutter: AuthInProgress
flutter: GoogleLoginSuccessful
flutter: GoogleLoginSuccessful

So my listener function is called twice for each state which really is a problem since my navigation is placed inside that listener some routes get pushed twice within my navigator.

My App is currently too complex and I haven't had enough time to investigate everything but it really seems like the bug just somehow was reintroduced. Maybe the Reproduce Part of the original issue already reproduces the issue again (I haven't checked yet).

@UliPrantz can you put together a sample app which reproduces the issue? Thanks!

@felangel I try to do it rn. Just could take some time. One simple question that I don't misunderstand the documentation: If my bloc yields the same state twice one after another (with no other state in between) then the listener function is only called once, isn't it? (My actual bloc doesn't do this but it would be easier to write the example app to reproduce the bug)

@UliPrantz yes, but only if the states can be compared and are evaulated as the same. Check package:equatable if you want to evaulate states with the same data as the same object. πŸ‘

@tenhobi ok the thing is I know equatable just a little bit. So the actual State that I'm using is super simple:

class BaseBlocState {}
class MyState extends BaseBlocState {
  @override
  String toString() => 'MyState';
}

and if the function body of my mapEventToState(BaseBlocEvent event) would look like this:

if (event is MyEvent) {
  yield MyState();
  yield MyState();
}

this would call the listener function only once as far as I understand.

So I just wrote the app accordingly to my description above and this would call the listener function twice I can also share the github project. (update: this is the git repo for the sample app https://github.com/UliPrantz/TestRepo)

@bobwiller BlocBuilder should only be used to return different widgets based on the bloc state. For things like navigation, showing alerts, showing SnackBars, etc ... you should use BlocListener. This is because BlocBuilder can be called many times by the flutter framework whereas BlocListener is only called once per state change. BlocListener is analogous to manually listening to changes in the bloc state and disposing the subscription.

Check out the SnackBar Recipe for more detail πŸ‘

Link Updated:
https://bloclibrary.dev/#/recipesfluttershowsnackbar

Was this page helpful?
0 / 5 - 0 ratings

Related issues

felangel picture felangel  Β·  32Comments

felangel picture felangel  Β·  35Comments

mikededo picture mikededo  Β·  28Comments

nosmirck picture nosmirck  Β·  30Comments

felangel picture felangel  Β·  27Comments