Could not find the correct provider above this widget
Provider Scope
MaterialApp
> provider(Screen A)
> Screen B
If Provider
is instantiated in Screen A, it won't be accessible in Screen B after a Navigator.push
from A → B.
Why?
Because Provider
is an InheritedWidget
and Navigator
uses MaterialApp context
outside its Screen A context
scope. (See Details below.)
Fix
Moving Provider
up to a common-parent, MaterialApp context
, allows both Screen A and B to inherit its state/context.
provider(MaterialApp)
> Screen A
> Screen B
Example
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// wrap MaterialApp in Provider widget
return ChangeNotifierProvider(
create: (context) => ColorModel(), // ← create/init your state model
child: MaterialApp(
home: ScreenA()
),
);
}
}
Details
Provider
-
Provider
is based onInheritedWidget
. Only child widgets can inherit parent widget's state.-
Provider
needs to be the root widget for any widget tree that wants access to your "provided" state object.
-
Navigator
-
Navigator.push(context)
on Screen A doesn't use thecontext
from Screen A.- It uses
context
fromMaterialApp
.
- It uses
-
Navigator.push(context)
is actuallyNavigator.of(context).push
-
Navigator.of(context)
means: search up this context hierarchy until you find a context that instantiated a Navigator- A default
Navigator
is instantiated inMaterialApp
. - Unless you explicitly create/use a different
Navigator
, you're using the default. -
Navigator
'scontext
is that ofMaterialApp
.
- A default
- Screen B will get that context (of
MaterialApp
), not the context of Screen A.- B is a sibling of A, not its child.
- B does not inherit from A, even though it appears "instantiated" inside
context
A. - Screen B is a child of
MaterialApp
context
, not of Screen Acontext
. -
Provider
context
scope, if defined in Screen A, doesn't cover Screen B
Screen A → B
Navigator.push(context, MaterialPageRoute(builder: (context) => ScreenB()))
is actually:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => ScreenB()))
which is like:
Navigator.of(MaterialApp).push(
MaterialPageRoute(builder: (MaterialAppContext) => ScreenB())
)
So Screen B is under MaterialApp context
, not under Screen A context
and therefore has no access to Screen A Provider
and its context
.
See this answer for a code sample to a similar question about Provider
.
Parameter context
for Provider.of(context) must be a child of you defined provider.
@override
Widget build(BuildContext context) {
// !important here, Scaffold.of(context) returns null
return Scaffold(
appBar: AppBar(title: Text('Demo')),
body: Builder(
builder: (BuildContext context) {
return FlatButton(
child: Text('BUTTON'),
onPressed: () {
// here, Scaffold.of(context) returns the locally created Scaffold
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Hello.')
));
}
);
}
)
);
}
Official sample: https://api.flutter.dev/flutter/widgets/BuildContext-class.html
This is my code:
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [ChangeNotifierProvider<SomeModel>(
create: (context){
return SomeModel();
},
),],
child: Builder(builder: (BuildContext context){
BuildContext rootContext = context;
return Container(
//Here to use rootContext is safe
//Provider.of<SomeModel>(rootContext, listen: false);
);
}),
);
}
Try extracting part of your widget tree that lies below Scaffold to a separate widget. The context you are using now is used to build your top level widget which does not Navigator yet.
The resulting code should look like that:
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: LoginWidget()
class LoginWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Container(
margin: EdgeInsets.all(8.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0))),
child: InkWell(
onTap: () {
var user = Provider.of<UserRepository>(context);
user.savePreference(user.user.id, "Something");
user.navigateToNewPage(Dashboard(), context);
print(user.user.id);
},
...
}
}