I am having difficulty understanding this structure. My idea is simple: I have a MultiBlocProvider in my build with two BlocProviders. The first one uses AuthBloc and immediately calls my GetToken, and the second one prepares my provider (TokenBloc).
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthBloc>(
create: (context) => sl()..add(const GetToken()),
),
BlocProvider<TokenBloc>(
create: (context) => sl<TokenBloc>(),
),
],
child: ...
);
}
In my StatefulWidget:
body: BlocBuilder<AuthBloc, AuthBlocState>(
builder: (_, state) {
if (state is AuthLoading) {
return const Center(child: CupertinoActivityIndicator());
}
if (state is AuthError) {
return const Center(child: CupertinoActivityIndicator());
}
if (state is AuthDone) {
return Container() // #MORE
}
...
}
)
So far, everything is fine. My container is only displayed when the state is AuthDone. However, in my widget, at #MORE, I have a button that should trigger (onTap) a method called _go():
_go() async {
BlocProvider.of<TokenBloc>(context).add(const GetToken());
}
Here everything is great; it works. However, I wanted to create a listener to track the states and at the same time execute other functions (without involving the creation of widgets). As far as I understood, the step was:
go() async {
BlocProvider<TokenBloc>(
create: (context) => sl()..add(const GetToken()),
child: BlocListener<TokenBloc, TokenBlocState>(
listenWhen: (previousState, currentState) {
debugPrint("E1");
debugPrint(currentState.data!.toString());
return currentState is TokenDone ||
currentState is TokenLoading;
},
listener: (_, state) {
debugPrint(state.toString());
if (state is TokenLoading) {
debugPrint("LOADING");
}
if (state is TokenError) {
debugPrint("ERROR");
}
if (state is TokenDone) {
debugPrint("DONE");
}
},
),
);
}
With this change, it no longer triggers my API or the BlocListener. This method needs to be separated from the widget because it will do something else.
I really thought I was following the right path. Any help is welcome.
The BlocListener
is a widget. It needs to be in the widget tree to properly work, the widget tree being what you return in the build
method of your widgets (or their states in case of stateful widgets).
Under the hood, BlocListener
gets the bloc/cubit from the context in initState
and listens on it, applies the condition listenWhen
and calls the listener
method.
If you want to create a listener on your cubit/bloc outside of the widget-tree, like on a button click, you can do it like you would with the Stream
:
BlocProvider.of<TokenBloc>(context).stream.listen((state) {
// state is of TokenBlocState type
});
The listen
method returns a StreamSubscription
that you need to cancel
when you no longer need the subscription.
But I see you're listening in the (what I assume is) some button callback. You probably want to check the current state of the bloc, not listen for the changes. Then you can just get the state
of the bloc like so:
final state = BlocProvider.of<TokenBloc>(context).state;
// state is of TokenBlocState type
Regarding the BlocProvider
widget that you put above BlocListener
in your go()
method - you probably want to use the bloc you already provided somewhere else, higher in the widget tree, not create a new instance. If the BlocProvider
was outside of your widget, you can get it through BlocProvider.of(context)
like in the example above. If it is in the same widget as the method, you need to pass to the .of()
method a BuildContext
that contains this provider, so wrap your widget with Builder
widget and pass the context
to your go()
method.