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?
@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:
create: (_) => MainBloc()..add(ReadyEvent())
in BlocProvider<MainBloc>
_mainBloc = BlocProvider.of<MainBloc>(context)
from initState()
So, I have next questions:
_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?@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:
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?state
generating. Then why it occurs when I add
event in initState()
, but not occurs when I add
it in the BlocProvider
constructor?easy_localization
, but at least with this too.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
create
gets called as soon as BlocProvider.of
is called.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!
Most helpful comment
@felangel it makes sense, thanks!