Bloc: Firing same state again with updated attributes to update list

Created on 28 Apr 2020  路  9Comments  路  Source: felangel/bloc

Hi Felix, I am using your bloc library for the first time in a project. Basically it's an app that mimics the search bar in Firefox next to the address bar (where it was possible to install multiple search engines). I have the following problem, I hope it's understandable without code:

Situation

  • Screen 1 (MainSearchScreen) shows a list of installed search engines. The user can open another screen (InstallEngineScreen) to install more search engines.
  • Screen 2 (InstallEngineScreen) now installs a new search engine and fires a new AddEngineEvent.
  • the EngineListBloc processes this event in mapEventToState and fires a new LoadedEngineListState with the updated search engines (now including the freshly installed search engine).

Problem

  • now the problem is this: When returning to the previous screen (which shows the installed search engines), the list of engines does not update.
  • Neither BlocBuilder nor BlocListener react in this situation.

Assumption

  • I think this is not actually an issue with the two screens, but would also happen when doing this in only one screen.
  • The reason seems to be that LoadedEngineListState was already the last/current state and firing the same state again (even with updated attributes) does not trigger a rebuild of the UI.
  • When using pure Streams and StreamBuilders this would just work by sending a new list of search engines into the sink.
  • It seems that I have to manually re-trigger a reload by firing another GetAllEnginesEvent, so that the BlocBuilder can rebuild its way through the events/states (LoadingEngineListState -> LoadedEngineListState).

I solved this in a way which I do not like too much:

  • The 2nd screen now has an onSave callback (in the InstallEngineScreen constructor), so that I can fire a GetAllEnginesEvent when returning from the InstallEngineScreen to the 1st screen.
  • This feels like coupling things too tightly and having to "fix" something that should be done by blocs/events/states.
  • I would much more like to solve this only by using states and events.

Is there a better solution to this or a pattern that I am missing?
Or can this be automatically taken care of by the bloc library?

question

Most helpful comment

@RollyPeres Thanks for the tip, that slims down the code and reduces errors, although IntelliJ warns you anyway when leaving them open.

I'll close this now as the problem is resolved.

All 9 comments

Hi @ben-lotze !

It's always harder to tell what's going on without a sample app. To me it sounds like you are not yielding a new list of engines. Basically the bloc will compare the previous state with the current state and if they're equal it won't emit that last state. You're either misusing equatable or most likely you're not yielding a new list of engines; you should avoid altering the existing one and just create a brand new list of engines before yielding your new loaded state.

If my suggestion doesn't solve your issue then feel free to share a sample app and we'll have a look at it!

Hi @ben-lotze 馃憢
Thanks for opening an issue!

Are you able to share a link to a sample app which reproduces the issue? As @RollyPeres mentioned, it'll be much easier to help pinpoint the issue and suggest a fix.

Hi @felangel and @RollyPeres I uploaded the app code to github: https://github.com/ben-lotze/search_buddy
I'll try to explaiin as detailled as possible where the relevant code locations are, to not waste your time.


case 1, works as expected:

  • In the MainSearchRoute there is a RecentSearchesList
  • now when a search is done, the SearchBloc adds a AddRecentSearchEvent to the SearchHistoryBloc
  • then the GetAllRecentSearchesEvent adds this to the history database and fires a GetAllRecentSearchesEvent to refresh the list
  • This works just as expected, the list of recent searches updates.

case 2 (should be the same as case 1, but it behaves differently)

  • The SearchEngineSelectionDrawer in the MainSearchRoute is showing a list of installed search engines.

_solution 1 (broken)_

  • everything is done as in case 1, but does not work here.
  • When the EngineListBloc receiives an AddEngineEvent, it yields Loading state and then fires a GetAllEnginesEvent afetr the database is done.
  • but the drawer does noto update here.
  • it does also not help to add a delay in the bloc, so that I first return to the drawer in the MainSearchRoute before the evennt gets fired.

_solution 2 (broken)_

  • In the EngineListBloc: When an AddEngineEvent comes in, I fire a LoadingEngineListState, and later a LoadedEngineListState once the database is done.
  • This also does not work.

_Tweak (broken)_

  • So I added a delay in the EngineListBloc (in both cases 1+2), so that I could return to the main screen, before the GetAllEnginesEvent (or the LoadedEngineListState) was fired from the bloc. Now I am staring at the drawer when the GetAllEnginesEvent is fired inside the EngineListBloc, but still no refresh is happening.
  • There must clearly be dark magic somewhere in my code, but I cannot find it.

For testing purposes I added a refresh button into the drawer. It just fires a GetAllEnginesEvent which works as expected and refreshes the list of installed search engines.

  • I am confused why firing the same event from the bloc itself does not have the same effect?

_solution 3 (works, currently inactive and uncommented)_
Since refreshing works (at least sometimes?) when the event is fired when staring at the drawer, I use a callback method, that is executed after returning to the MainSearchRoute from the InstallEngineScreen.
the two relevant code locations are:

  • SearchEngineSelectionDrawer#_buildInstalledSearchEngineList: There is an "Add engine button with an onReturn parameter, which will fire the GetAllEnginesEvent after returning from the InstallEngineScreen
  • _InstallEngineScreenState#dispose (to call the onReturn method) -> this is currently uncommented

@ben-lotze I wasn't able to run your code on my machine, there's androidx issues. Mind upgrading your project to be androidx compatible?

@RollyPeres Oh, sorry, I updated the project to AndroidX. Hope it works now.
And thanks so much for your time!

Thanks for updating @ben-lotze , it runs now, however I keep getting Invalid argument(s): Record key cannot be null
Is it possible to create a minimal sample reproducing your issue ? Would help pinpointing the issue if you clean up all the unrelated dependencies so we can focus on the bloc related aspects without bumping into external factors.

@RollyPeres @felangel
Ok, this is a bit embarrassing. While coding the simplified example I realized the error in the app I linked above:

  • I provided the relevant Bloc two times!
  • one time above the Material app, so that both screens would find it.
  • one time on the main page (where the Drawer would not update). This was a bad mistake, which broke the event handling.
  • I did not even realize that I had done this until now. This could clearly not work.

So, this is to potential later readers. Please correct me if I got this wrong. What happed was this:

  • The 2nd screen (where new objects could be added) would find the bloc which was provided above the MaterialApp.
  • When returning from that 2nd screen to the main screen, the main screen would use a different bloc (directly provided on the main screen). This version of the bloc never received an event and so never realized that there was a state change. So it just returned the outdated objects which it knew.
  • When explicitly calling refresh from the Main page, this would trigger a fresh pull from the database, which would finally update the list of search engines.

So, problem solved. Sorry for the inconvenience. But a thousand thanks anyway.
Otherwise I would not have started an unfinished mini example that opened my eyes.

And it's just great how you guys are helping out here when problems occur. Keep up the great work.

@ben-lotze glad you found the solution yourself!

From having a quick peak at your app earlier, I'd suggest not manually creating blocs in initState and disposing yourself, you can leave that up to the BlocProvider to take care of.
So basically you can create a bloc directly in the build around the lines of:

BlocProvider(create: (_) => SearchBloc(someRepository: context.repository<SomeRepository>()))

Good luck with your project and feel free to come back if you encounter additional issues. 馃憤

@RollyPeres Thanks for the tip, that slims down the code and reduces errors, although IntelliJ warns you anyway when leaving them open.

I'll close this now as the problem is resolved.

Was this page helpful?
0 / 5 - 0 ratings