Bloc: Can't get new state in BlocListener due to localizations

Created on 18 Apr 2020  路  30Comments  路  Source: felangel/bloc

Hello.
I have an application with bloc and localizations. My app loads SplashPage until everything is ready, and after that it changes the page. All fine and obvious without localizations(without supportedLocales, localizationsDelegates and localeResolutionCallback in MaterialApp), but when it is turned on, a new state will not be received and the SplashPage will not change to the FirstPage.
Please check minimal working example here.
I found the same issue here, but unfortunately published solution is not working.
How can I fix it?

question

Most helpful comment

@felangel it makes sense, thanks!

All 30 comments

@felangel I also get the same problem with the easy_localization package, please check this

@felangel here is a suggestion to use BlocBuilder instead of BlocListener for routing, and remark that my BlocListener didn't see new state because I send an event in initState(). But I'm not sure it is true, because all works just fine without localizations and I can even explicitly add sleep(Duration(seconds:5)) in the build(), and the BlocListener can still hear this "late" new state.
Please, can you answer is this a bug or a problem with my minimal working example?

Hi @don-prog 馃憢
Thanks for opening an issue and sorry for the delayed reply! I will take a look asap 馃憤

@felangel many thanks! I'm really looking forward to the information about this, because this can block my current release application, and it is ironic.

@don-prog I've opened a PR which fixes your issue. You grabbing the bloc in initState ended up hitting _bloc.skip(1) so your first state was never handled by the listener.

Thanks so much for helping out @RollyPeres 馃檹
@don-prog closing this issue for now but feel free to comment with any additional questions 馃憤

@RollyPeres thanks for your efforts. Now I can get the new state with this:

  1. Add create: (_) => MainBloc()..add(ReadyEvent()) in BlocProvider<MainBloc>
  2. Delete _mainBloc = BlocProvider.of<MainBloc>(context) from initState()

So, I have next questions:

  1. Is grabbing _mainBloc = BlocProvider.of<MainBloc>(context) in initState() is incorrect usage of bloc? Why it is documented here? If it is incorrect, how should I do it?
  2. This behaviour is not observed without localization, why does this happen when it is turned on?

@felangel please, check my previous comment! I still just cannot use this solution - I obviously need MainBloc instance in widget.

@don-prog you can grab the instance still but just don't add the event in initState because it's too early and BlocListener misses it because it isn't listening yet. Also, you can just do the lookup directly in build using context.bloc<MainBloc>() so it's not necessary to maintain the reference in the State

you can grab the instance still but just don't add the event in initState because it's too early and BlocListener misses it because it isn't listening yet.

@felangel I already tried it, but this scheme just doesn't work:

create: (_) => MainBloc()..add(ReadyEvent()),
...
  @override
  void initState() {
    super.initState();

    _mainBloc = BlocProvider.of<MainBloc>(context);
  }

@don-prog yeah that won't work actually because of the same issue. I would highly recommend just looking up MainBloc in your build method instead like:

@override
Widget build(BuildContext context) {
  final mainBloc = BlocProvider.of<MainBloc>(context);
  ...
}

Haven't had the chance to look too much into this, but the key thing is that when that package creates it's provider and you add the event in initState, it has the side effect of mapEventToState being called before the listener has the chance of subscribing to state. so basically it will skip the state you're interested in, instead of the initial one.

You should add it the way I suggested and grab the bloc in build method just like @felangel suggested above. It's O(1) time-space complexity so you're fine.

@RollyPeres I understand what you said and I understand how I can workaround this. But:

  1. The problem is not solved when I just don't add event in the initState(). The problem occurs due to _mainBloc = BlocProvider.of<MainBloc>(context) in the initState(), i.e. I just save the bloc in the field. Isn't this at least strange?
  2. Let's assume that the problem occurs because of early state generating. Then why it occurs when I add event in initState(), but not occurs when I add it in the BlocProvider constructor?
  3. Why does this happen only with localizations? Also, it occurs not only with the easy_localization, but at least with this too.
  4. Let's assume that the problem occurs because of early state generating. But without localization I can even sleep() in the build() and after that get the "late" state. Why?

@felangel are you planning to reopen this issue?

@don-prog

  1. That is because BlocProviders are lazy by default so the create gets called as soon as BlocProvider.of is called.
  1. Since the add is within the create it is only called when the bloc is requested (see above)

3 and 4. Likely stem from 1 and 2.

@felangel ok, I understood. But let's assume another situation. I have service locator and my bloc is already created. So, I use:

BlocProvider<MainBloc>.value(
          value: sl()..add(ReadyEvent()),
          ...

Now I can't get the new state using this workaround.
In fact, I use such a scheme on the release app, so I'm not asking out of curiosity.
You can check this commit for the full example.
Which workaround can I use here?

@don-prog this isn't really a workaround it's more how BlocListener works.

Your BlocListener will only get notified of state changes that occurred after it started listening. With that in mind it's totally up to you to decide how you want to structure your application.

In the scenario you've described it's really tough to rely on BlocListener for logic when the bloc may or may not have finished processing the event.

I personally don't use service locators and instead rely on BlocProvider for injecting blocs into the widget tree. Is there a reason you decided to go with the service locator approach instead of relying on InheritedWidgets?

@felangel ok, let's assume that I don't use sl, but I want to use singleton bloc. Then I create it with BlocProvider at the top of the widget tree, and don't use sl at all. This will do the same thing: my bloc is already created and I don't know where can I add event and get state in the listener?

@don-prog I use get_it and injectable to create instances based on environment and I never had problems like this. I believe that the localization package you're using simply doesn't play well with bloc. They're wrapping all the widget tree in a stream builder which will run after the first frame; you might wanna try and reproduce in a simple example if that's not causing any unwanted side-effect.
I also never trust a package containing easy in it's name. If I wanna see magic I search for any Chinese magician from Got talent馃槀

Yeah I agree with @RollyPeres. If you can create a simple example without a dependency on the localization package it would be awesome.

@RollyPeres I also use get_it as sl. I understand your irony about the easy_localization name :), but I also used raw localization, it's the same, as I said.

@felangel my first post used raw localization, not easy_localization

@don-prog I opened a pull request to fix the first example.

@don-prog I'm indeed making fun of the name, but nothing against their work. I never used it since I prefer to stay away from too many third party packages.

@felangel thanks, I already understand your solution :) But let me show you the commit with using service locator and raw localization, here I still have the same problem even with your suggestion. I need sl and MainBloc as singleton for the typical di things(e.g. add event to MainBloc without context), so MainBloc really should be singleton. What solution I need to use here?

@RollyPeres I provided the new commit above with GetIt, maybe you can suggest something?

Fixed it https://github.com/don-prog/flutter_localization_bloc_bug/pull/2
If you provide it using BlocProvider.value it will eagerly use the instance from sl and add your ready event on top of it so by the time the bloc listener subscribes, it will skip(1) on your ready state, which you don't want.

@RollyPeres thanks, I think this is what I need! Also, I understand that unnamed BlocProvider constructor will lazily call my bloc instance, but when exactly will the create() function be called in my test repo project?

@don-prog create will get called as soon as the bloc is looked up via BlocProvider.of.
This happens internally within BlocBuilder/BlocListener as well so as soon as one of those widgets is mounted the bloc will get created if you haven鈥檛 explicitly called BlocProvider.of already.

@felangel it makes sense, thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

shawnchan2014 picture shawnchan2014  路  3Comments

timtraversy picture timtraversy  路  3Comments

komapeb picture komapeb  路  3Comments

abinvp picture abinvp  路  3Comments

rsnider19 picture rsnider19  路  3Comments