Flutter Bloc change state from different widget - flutter

On my apps home page, I have a list view that rebuilds whenever the user clicks on a new page.
I have implemented the block pattern using the flutter_bloc plugin but I don't know how to change the state from another widget.

Two things you will have to keep in mind for changing state with flutter bloc:
Dependency injection (DI) of a bloc.
Interaction with your bloc instance.
Dependency injection of a bloc
Case 1. You need to provide bloc to widget subtree within one route.
To provide a single instance of a bloc to multiple widgets within a subtree you use BlocProvider widget. It creates bloc instance, automatically disposes of it when needed and provides bloc to its children via BlocProvider.of<T>(context), where T is the name of your bloc:
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
Keep in mind, that by deafult it is created with property lazy: true, means that create: (BuildContext context) => BlocA(), will be executed after invoke of BlocProvider.of<T>(context). If you dont want it - set lazy: false in advance.
Case 2. You need to provide bloc to widgets from another route (to another context).
BlocProvider automatically disposes of a bloc instance with context of new route instantiated, but that will not happen if you use BlocProvider.value:
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);
Important note: BlocProvider.value should only be used for providing existing instances to new subtree, do not create Bloc instance with it
Interaction with your bloc instance
Starting from bloc v6.1.0 context.bloc and context.repository are deprecated in favor of context.read and context.watch.
context.select allows to update UI based on a part of a bloc state:
final name = context.select((UserBloc bloc) => bloc.state.user.name);
context.read access a bloc with a BuildContext and do not result rebuilds.
context.watch gets a value from the nearest ancestor provider of its type and subscribes to the provider.
To access the bloc's state
If you need a widget rebuilding due bloc value changing use context.watch or BlocBuilder:
// Using context.watch at the root of the build method will result in the entire widget being rebuilt when the bloc state changes.
#override
Widget build(BuildContext context) {
final state = context.watch<MyBloc>().state;
return Text('$state');
}
or with BlocBuilder:
// If the entire widget does not need to be rebuilt, either use BlocBuilder to wrap the parts that should rebuild
#override
Widget build(BuildContext context) {
return BlocBuilder<MyBloc, MyState>(
builder: (context, state) => Text('$state'),
);
}
To access the bloc so that an event can be added
Use context.read:
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => context.read<MyBloc>().add(MyEvent()),
...
)
}

You can place the BlocProvider to a common ansestor like as a parent of your MaterialApp widget, or you can pass the bloc to your new page like this:
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenB(),
);

Related

Provider not found Flutter

I want to share some data across different widgets so I decided to use a ChangeNotifierProvider<Example> with its relative Consumer<Example>. I have already used Providers before but never in this way (in fact I got some errors).
ChangeNotifierProvider<Example> has been defined in menu page while Consumer<Example> in an other widget defined in menu too.
Menu page :
class Menu extends StatefulWidget {
//...SOme code
ChangeNotifierProvider<Example>(
create: (context) => Example(),
child: ShowMultipleAnswers()
//...some code
And now I would like to use Consumer<Example> inside ShowMultipleAnswers() widget consuming data created in menu like :
class ShowMultipleAnswers extends StatefulWidget {
//...some code
Widget build(BuildContext context) {
return Consumer<Example>(builder: (context, handler, child) {
//some code
But I got these errors :
Error: Could not find the correct Provider<Example> above this Consumer<Example> Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
consider using `builder` like so:
Make sure that Consumer<Example> is under your MultiProvider/Provider<Example>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
```
```
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
```
I think the most valid options are 2 :
- The provider you are trying to read is in a different route.
But I don't know because they should be in the same, I mean ShowMultipleAnswers() is the Provider child.
Or the second
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
In this case, following suggestions above, I should use a builder:(context){} instead of directly calling child : .. but I read that after provider 5 builder has been substituted by create so I'm confused.
If I'm using wrong widgets tell me please!
The error message exactly specifies and describe your error
ShowMultipleAnswers got built using Menu context which doesn't have the Example provider, thus it throws this error.
you can either use builder attribute instead of child or wrap your MaterialApp with the provider
your code should be something like this:
class Menu extends StatefulWidget {
//...SOme code
ChangeNotifierProvider<Example>(
create: (context) => Example(),
builder: (context) {
return ShowMultipleAnswers();
}
//...some code
I forgot to say that there was an other page between Menu and ShowMultipleAnswers, like a bridge between them so the real flow was :
Menu Page -> Bridge Page -> ShowMultipleAnswers Page.
I removed this Bridge Page and it worked! But I still don't understand why it didn't work, maybe because Bridge Page didn't have any references of its Provider?

Flutter: Connectivity Widget Wrapper Provider error

I've added ConnectivityWrapperWidget with my custom offlineWidget. I'm getting this error, while running the app
Error: Could not find the correct Provider above this ConnectivityWidgetWrapper Widget
This likely happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that ConnectivityWidgetWrapper is under your MultiProvider/Provider.
This usually happen when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
I don't get it. Can somebody explain, what is the issue
Try to use builder instead of child, as your error suggests. If it doesn't help, show some code.
While addig ConnectivityWrapper, if using custom offlineWidget(), then it was necesaary to add ConnectivityAppWrapper as a parent of MaterialApp.

Dart Provider: not available at the immediate child

#override
Widget build(BuildContext context) {
return BlocProvider<HomeBloc>(
create: (context) {
return HomeBloc(homeRepo: HomeRepository());
},
child: BlocProvider.of<HomeBloc>(context).state is HomeStateLoading
? CircularProgressIndicator()
: Container());
}
I am confused with the error:
BlocProvider.of() called with a context that does not contain a Bloc of type HomeBloc.
No ancestor could be found starting from the context that was passed to
BlocProvider.of<HomeBloc>().
Didn't I just create the HomeBloc at its immediate parent? What does it want?
You are using the context passed into the build method of your widget class to look for a parent BlocProvider. However, that context is the widget tree as far as your widget class sees it. Because of this, your BlocProvider.of is looking for a BlocProvider that is a parent of your widget class. If you want to get the provider that is the immediate parent, you need a new context object in which the BlocProvider is an ancestor in the widget tree. The easiest way to do this is with a Builder widget:
#override
Widget build(BuildContext context) {
return BlocProvider<HomeBloc>(
create: (context) {
return HomeBloc(homeRepo: HomeRepository());
},
child: Builder(
builder: (newContext) => BlocProvider.of<HomeBloc>(newContext).state is HomeStateLoading
? CircularProgressIndicator()
: Container(),
),
);
}
That being said, it's pretty redundant to create a provider and then immediately reverence the provider. Providers are for retrieving stuff further down the widget tree, not typically for immediate descendants. In this case, using a provider is overkill and there isn't really any reason to not just have the bloc be a field of your class and reference it directly.
From the documentation :
The easiest way to read a value is by using the static method
Provider.of(BuildContext context).
This method will look up in the widget tree starting from the widget
associated with the BuildContext passed and it will return the nearest
variable of type T found (or throw if nothing is found).
In your case it starts looking up the widget tree from your whole widget (associated to the BuildContext).
So you need to move your BlocProvider to be an ancestor of this widget.
If for some reason this is not possible, you can use Consumer, which allows obtaining a value from a provider when you don't have a BuildContext that is a descendant of the said provider.
Read https://pub.dev/documentation/provider/latest/provider/Consumer-class.html

BlocProvider.of() called with a context that does not contain a Bloc of type MainBloc

I have a MainBloc that resides inside a main route, this route has a bottom app bar with multiple sub-routes, I want the same BLoC to run on all five sub-routes so that when one of them changes the state of the block the others will see the effect.
I tried this SO question but its really far from what I'm looking for, also I tried following what the error advised me to, but didn't work, here is the message that I got:
This can happen if:
1. The context you used comes from a widget above the BlocProvider.
2. You used MultiBlocProvider and didn't explicity provide the BlocProvider types.
Good: BlocProvider<MainBloc>(builder: (context) => MainBloc())
Bad: BlocProvider(builder: (context) => MainBloc()).
Main route:
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<MainBloc>(
builder: (BuildContext context) => MainBloc(),
),
BlocProvider<OtherBloc>(
builder: (BuildContext context) => OtherBloc(),
),
],
child: /..., //here I have the bottom app bar with 5 buttons to navigate between sub-routes
);
one of the sub-routes:
#override
Widget build(BuildContext context) {
final MainBloc bloc = BlocProvider.of<MainBloc>(context);
return /...; //here I have the context of this sub-route.
}
from what I've seen from tutorials and articles this code should work, but I can't seem to find why not.
The problem is you cannot access InheritedWidgets across routes unless you provide the InheritedWidget above MaterialApp. I would recommend wrapping your new route in BlocProvider.value to provide the existing bloc to the new route like:
Navigator.of(context).push(
MaterialPageRoute<MyPage>(
builder: (_) {
return BlocProvider.value(
value: BlocProvider.of<MyBloc>(context),
child: MyPage(),
);
},
),
);
You can find more detailed information about this in the bloc documentation
As this child has the bottom app bar:
child: /..., //here I have the bottom app bar
then I assume that the MultiBlocProvider(..) is not wrapping the whole part of app which is using this Bloc, my suggestion here is to wrap the "MaterialApp" with "MultiBlocProvider".
return MultiBlocProvider(
providers: [..],
child: MaterialApp(..) // Set MaterialApp as the child of the MultiBlocProvider
//..
)

flutter bloc streambuilder with refreshindicator rebuild twice

Widget build(BuildContext context) {
final blocData = WeatherBlocProvider.of(context).bloc;
if (WeatherBloc.permission == true) {
blocData.forceRefreshAll();
return Container(
child: StreamBuilder(
stream: blocData.zipAll,
builder: (scontext, snapshot){
//to do
}
now i am using bloc pattern with streambuilder
and when i refresh parent widget i can see blocData.forceRefreshAll() this line is requested twice.(i mean build method is requested twice) how can i make only one?
i saw unwanted rebuild subject and they said use instance or initstate but with bloc pattern i think using initstate is not possible and const value is not working with
blocData.forceRefreshAll()
build method is meant to build widget tree, it can be called multiple times for various reasons. This is why should not fetch data in build.
If you can't access bloc in initState because there is no context yet - override another method, didChangeDependencies. It's called right after initState and it can use context, so you can access bloc provider with it.