Bloc: BlocListener receives the same state twice

Created on 8 May 2020  ยท  20Comments  ยท  Source: felangel/bloc

I have an issue with repeated states in BlocListener.
I have two blocs: MainBloc which any page can use, and FirstPageBloc only for FirstPage.
I also have two pages: FirstPage and SecondPage. Each page has only one button "Go to another page". And I disabled the MaterialPageRoute animation for them, so you can change pages very quickly.
The flow:

  1. FirstPage.initState() adds FirstFirstPageEvent(event of FirstPageBloc).
  2. Then FirstPageBloc adds FirstMainEvent (event of MainBloc).
  3. Then MainBloc yields FirstMainState.
  4. FirstPage has BlocListener which listen for FirstMainState, so it receives FirstMainState.

If you change pages from FirstPage to SecondPage slowly then all fine, but when you use literally double click for it(one click per one page change) then FirstPage will receive FirstState twice, so you can see two in a row log messages First page received FirstState. But MainBloc yields FirstMainState only once.

Please, check this minimal working example app.

question

Most helpful comment

Fixed it in https://github.com/don-prog/bloc_repeated_states_issue/pull/2

Your issue happened because when you're quickly switching the tabs back to first page, the new first page gets created but the previous one is not yet disposed, so the bloc listener will react twice(once from the initial first page instance and second time from the new instance).
Basically the tabs take a bit to switch so you can counter this side effect by overriding transformEvents and debouncing a bit your event, to ensure your tabs have time to dispose the previous instance. ๐Ÿ‘

All 20 comments

Hi @don-prog โœŒ

I had a quick look at your sample and from what I see you're navigating to second page with pushReplacementNamed which will destroy your first page then create the second one.
In your second page you're doing pushNamed on first page, which means your first page gets created again.
This is really important because your initState in your first page will run twice, thus adding that event twice.
Consider navigating to second page using pushNamed and going back to the first page using pop.

Hope that helps! ๐Ÿ‘

Closing for now but feel free to comment with additional questions if you're still having trouble ๐Ÿ‘
Thanks so much @RollyPeres for taking the time to answer ๐Ÿ™

@RollyPeres thanks for your suggestion! But:

  1. The various Navigator commands in the FirstPage and SecondPage - just a typo which I fixed in the last commit, and this does not change anything.
  2. Of course, I understand that these statements will call _FirstPageState.initState() again, but not twice. What I mean:
    If you click "Go to second page" and then "Go to first page" slowly, then all fine, but if you do it quickly, then you will see these logs:

I/flutter (28302): _FirstPageState.initState()
I/flutter (28302): _firstPageBloc.add(FirstFirstPageEvent())
I/flutter (28302): FirstPage.buildBody()
I/flutter (28302): sl().add(FirstMainEvent())
I/flutter (28302): yield FirstState
I/flutter (28302): First page received FirstState
I/flutter (28302): First page received FirstState

So, you can see two First page received FirstState messages, but only one message for the rest of the commands.
Does that make more sense?

I've opened a PR https://github.com/don-prog/bloc_repeated_states_issue/pull/1

Since you haven't detailed what exactly you're trying to achieve, I assumed you want to be able to add an event when you come back from second page, so I showcased how that could be achieved.
With my implementation the first event is only added once, when the first page is created.

Hope it solves your issue ๐Ÿ‘

@RollyPeres thanks for your PR! But:

  1. I understood your idea about pop(), but I can't use this scenario. The real app has BottomNavigationBar, when the user clicks on the tab then old tab should be closed. Buttons in this app - simulation of the tab buttons, so I can't just pop, because the stack is already clear.
  2. "Since you haven't detailed what exactly you're trying to achieve". I just need to get rid of the this states duplication with the existing Navigator scenario.

@don-prog You still haven't detailed what you want to achieve exactly. If you need to achieve something around a BottomNavigationBar then I'd advice you to submit your exact scenario as an example with a clear and concise explanation of what you're trying to achieve.

@RollyPeres I just want to achieve basic bottom navigation with a clear stack(clicking on the tab button not just pushes, but also clears the stack). Please, check the new commit.

You can achieve that pretty easy without needing to explicitly navigate.
This is one quick example I found: https://medium.com/@uncoded_decimal/creating-bottom-navigation-tabs-using-flutter-2286681450d4
By default a tab will be destroyed when navigating away and recreated when going back to it.

Hope this helps you achieve your desired outcome ๐Ÿ‘

@RollyPeres thanks again, but this is just a workaround. I have seen many similar things.
Besides, what if I want to use a described Navigator scenario even just with buttons. Is it incorrect and why?
Do you know how to solve the described problem or what exactly causes it?

@don-prog it's not a workaround, it's a standard way of using tabs in a bottom navbaresque way.

I'm sorry but I really can't understand where you're going with all this. I don't even see it being related to the bloc package tbh. It's all about how you use the navigation to move between tab pages -- that is if you want to explicitly handle navigation. In case you use TabBar then a TabController will handle switching between tabs.

Maybe someone else can better understand your needs ๐Ÿ˜ƒ

@RollyPeres I really can't understand why we are talking about bottom navigation. Are

 _navigatorKey.currentState.popUntil((route) => route.isFirst);
 _navigatorKey.currentState.pushReplacementNamed('route', arguments: arguments);

commands incorrect for bloc? Why does this even lead to BlocListener receives the same state twice? Do you know that?

How you navigate has nothing to do with the bloc. Your first call is unneeded since you'll always be replacing the existing route thereafter.
I didn't receive two states when I was running your sample. Try a flutter clean.

@RollyPeres

  1. "Your first call is unneeded since you'll always be replacing the existing route thereafter". You are absolutely correct in the case when we have only one route in stack, but it's not correct when the stack has multiple routes.
  2. Thanks for your suggestion, but, of course, I tried flutter clean and just in case I tried it again now.
  3. I think this is because you didn't switch between tabs fast enough. Try using two fingers to switch between tabs and you will get this behaviour.

@don-prog I'm aware you're having that issue whenever you're tapping quickly, but as already mentioned, I have tested that too and I don't get multiple states. I suggest you manually remove the app from your device + flutter clean. ๐Ÿคž

@RollyPeres

  1. " I suggest you manually remove the app from your device + flutter clean". For what? I already have this issue with the real app. This is a new project that was installed only after an issue occurred. Anyway, I did it if you want so. This doesn't work.
  2. I achieve this issue on different real devices.
  3. I have provided helper function for you, check the last commit. I achieve this issue on different devices when elapsed time for changing tabs ~150ms or less(but I think this may vary depending on the performance of the device):

I/flutter (19741): Elapsed milliseconds for changing tabs: 158
I/flutter (19741): _FirstPageState.initState()
I/flutter (19741): _firstPageBloc.add(FirstFirstPageEvent())
I/flutter (19741): FirstPage.buildBody()
I/flutter (19741): sl().add(FirstMainEvent())
I/flutter (19741): yield FirstState
I/flutter (19741): First page received FirstState
I/flutter (19741): First page received FirstState

Fixed it in https://github.com/don-prog/bloc_repeated_states_issue/pull/2

Your issue happened because when you're quickly switching the tabs back to first page, the new first page gets created but the previous one is not yet disposed, so the bloc listener will react twice(once from the initial first page instance and second time from the new instance).
Basically the tabs take a bit to switch so you can counter this side effect by overriding transformEvents and debouncing a bit your event, to ensure your tabs have time to dispose the previous instance. ๐Ÿ‘

@RollyPeres finally, thanks. I already use debounce as hot fix, but I thought that there may be some better solution.

@don-prog I don't think this has anything to do with bloc. It seems like your complaint is more about how/when tabs are disposed. You might want to bring this up as an issue on the Flutter repo if you feel it's undesirable behavior.

@felangel now I don't really think that this is bloc issue or problem :)
Also, I ready to report this issue/undesirable behaviour to the Flutter repo, because I think this may be so.
But I think I haven't much practical experience in the Flutter's under the hood mechanisms, as you have. So, I also want to know your opinion about this behaviour: how incorrect is it and should I report it?

@don-prog I think this behavior is expected but that being said it's not unreasonable to open an issue and see what the Flutter team thinks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fvisticot picture fvisticot  ยท  31Comments

nosmirck picture nosmirck  ยท  30Comments

windinvip picture windinvip  ยท  39Comments

basketball-ico picture basketball-ico  ยท  35Comments

zs-dima picture zs-dima  ยท  34Comments