import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class Data extends ChangeNotifier {
String texto = 'Mi secreto';
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(Provider.of<Data>(context).texto),
),
body: Level1(),
),
),
);
}
}
class Level1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Level2();
}
}
class Level2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(Provider.of<Data>(context).texto),
Level3(),
],
);
}
}
class Level3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topCenter,
child: Text(
'h',
textAlign: TextAlign.center,
),
);
}
}
This is the error I get:
ββββββββ Exception caught by widgets library βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The following ProviderNotFoundException was thrown building MyApp(dirty):
Error: Could not find the correct Provider<Data> above this MyApp Widget
To fix, please:
* Ensure the Provider<Data> is an ancestor to this MyApp Widget
* Provide types to Provider<Data>
* Provide types to Consumer<Data>
* Provide types to Provider.of<Data>()
* Ensure the correct `context` is being used.
If none of these solutions work, please file a bug at:
https://github.com/rrousselGit/provider/issues
As long as I switch "Provider.of(context).texto" for a dumby text e.g. 'dumby text', everything works fine it even shows the "Provider.of(context).texto" well in Level 2.
I'm using 4.0.4 version
I just learned why it happens. It's because ChangeNotifierProvider cannot have as a child MaterialApp directly but Consumer as following:
ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: Consumer<Data>(
builder: (context, provider, child) => MaterialApp(
So, it cannot be (like I originally had):
child: MaterialApp()
I know it used to work simpler (just with the child: MaterialApp()) in the past but not any more. I don't understand why. Can anyone explain that?
Thanks,
The solution I provided above has one downside.listen: false is not functional anymore. If you write this in any of the sections, it will still listen:
Provider.of<Data>(context, listen: false).texto
Any ideas on how to fix that?
Here is the full code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: Consumer<Data>(
builder: (context, provider, child) => MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(Provider.of<Data>(context, listen: false).texto),
),
body: Level1(),
),
),
));
}
}
class Level1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Level2();
}
}
class Level2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextField(
onChanged: (newValue) {
Provider.of<Data>(context, listen: false).changeString(newValue);
},
),
Level3(),
],
);
}
}
class Level3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topCenter,
child: Text(
Provider.of<Data>(context, listen: false).texto,
textAlign: TextAlign.center,
),
);
}
}
class Data extends ChangeNotifier {
String texto = 'Mi secreto';
void changeString(String newValue) {
texto = newValue;
notifyListeners();
}
}
I think you can make it better by doing this:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Selector<Data, String>(
selector: (_, provider) => provider.texto,
builder: (context, texto, child) => Text(texto),
),
),
body: Level1(),
),
);
}
}
and updated Level3 to use Selector also.
Great, thanks @medyas. It looks like the main function is enough to solve the original issue. So here is the full working code. Answer to my original question:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(Provider.of<Data>(context, listen: true).texto),
),
body: Level1(),
),
);
}
}
class Level1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Level2();
}
}
class Level2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextField(
onChanged: (newValue) {
Provider.of<Data>(context, listen: false).changeString(newValue);
},
),
Level3(),
],
);
}
}
class Level3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topCenter,
child: Text(
Provider.of<Data>(context, listen: false).texto,
textAlign: TextAlign.center,
),
);
}
}
class Data extends ChangeNotifier {
String texto = 'Mi secreto';
void changeString(String newValue) {
texto = newValue;
notifyListeners();
}
}
From what it looks like, it doesn't even matter whether the state that I pass goes above/below in THE WIDGET TREE but what matters whether it goes above/below THE CLASS (in a different class). When I had
ChangeNotifierProvider in the widget tree above BUT still within the same classβit didn't work. As soon as I moved it up the class (in the main function), it suddenly worked.
Closing as it seems to be fixed
@rrousselGit but you still haven't answered a few concerning questions:
If you don't answer, it's hard to know whether it's intentional and if it isβwhy? Or it is just rather a bug.
Why can't we wrap MaterialApp widget with Provider? In other words, why doesn't it work when we do that? see more comments above
You can. The problem is you used the wrong BuildContext: the context of MyApp, which is an ancestor of the provider and therefore do not have access to the provider
Thanks @rrousselGit for your answer. So, then I guess the only solution is to wrap MyApp then like this:
void main() {
runApp(ChangeNotifierProvider<Data>(
create: (context) => Data(),
child: MyApp(),
),
);
}
Or is there any other way (a better way) to wrap the MaterialApp Widget for it to work with a different context?
What's good
Or you can use the Builder widget