【Flutter】Provider.of(context)で「ProviderNotFoundException」が発生したときの対処
状態管理パッケージのProviderを使用する際に、切り出した関数でProvider.of(context, listen: false)
を呼んで、ProviderNotFoundException
(BuildContextの中にProviderが見つからない)というエラーを発生させたことが何回かあったので、備忘録を残す。
The following ProviderNotFoundException was thrown while handling a gesture:
Error: Could not find the correct Provider<MyStore> above this MyHomePage Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
(最近はRiverpodが主流なのと、最新のProviderではcontext.read<T>()
とcontext.read<T>()
があるので、この問題にあまり遭遇しないかもしれないけど。)
現象
【※Flutterのサンプルアプリで説明】
以下のコードのように_incrementCounter()
関数の中で、ChangeNotifier
のクラスのインスタンスを使うとき、Provider.of<T>()
で呼び出す。
しかし、以下のように必要な引数のcontext
は渡しているのに、「MyHomePageWidgetにProvider<Mytore>
が見つかりません。そのBuildContext
にProviderは含まれていません」というエラーが出力されてしまう。
class MyHomePage extends StatefulWidget {
// 省略
}
class _MyHomePageState extends State<MyHomePage> {
void _incrementCounter() {
// ここでエラー↓
final store = Provider.of<MyStore>(context, listen: false);
store.addCounter();
}
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => MyStore(counter: 0),
builder: (BuildContext context, _) {
return Scaffold(
// 省略
floatingActionButton: FloatingActionButton(
onPressed: () => _incrementCounter(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
},
);
}
}
【エラーメッセージ】
The following ProviderNotFoundException was thrown while handling a gesture:
Error: Could not find the correct Provider<MyStore> above this MyHomePage 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.
Make sure that MyHomePage is under your MultiProvider/Provider<MyStore>.
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>().toString()),
);
}
```
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, child) {
// No longer throws
return Text(context.watch<Example>().toString());
}
);
}
```
原因と解消方法
エラーの原因は、Provider.of()
に渡しているcontext
が間違っているため。
以下のように、_incrementCounter()
の引数でChangeNotifierProvider
のbuilderプロパティのcontext
を受け取るようにすれば、エラーは解消する。
class MyHomePage extends StatefulWidget {
// 省略
}
class _MyHomePageState extends State<MyHomePage> {
// BuildContextを受け取る↓
void _incrementCounter(BuildContext context) {
final store = Provider.of<MyStore>(context, listen: false);
store.addCounter();
}
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => MyStore(counter: 0),
builder: (BuildContext context, _) { // ←ここのcontextを渡す
return Scaffold(
// 省略
floatingActionButton: FloatingActionButton(
// ChangeNotifierProviderのbuildプロパティのcontextを渡す
onPressed: () => _incrementCounter(context),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
},
);
}
}
ofメソッドには正しいBuildContextを渡す
BuildContextは、親WidgetのElementを参照するデータを持っていて、親Widgetから子孫のWidgetの情報を持っている。
Provider.of()
のofメソッドは、親Widgetから祖先のWidgetを辿る。
エラーの原因となっているProvider.of<T>()
は、引数のcontext
の親Widgetから祖先を辿って、生成したChangeNotifierのクラスのインスタンスを取得する。
なので、Provider.of<T>()
にcontext
を渡す場合は、そのcontext
から祖先の間でChangeNotifier
のインスタンスが生成されている必要がある。
しかし最初のコード↓だと、Provider.of<MyStore>(context, listen: false)
に渡しているcontext
は、framework.dartクラスで定義されている最上位のcontext
なので、そこから祖先を辿っても目的のインスタンスは見つからない。
void _incrementCounter() {
// 引数で渡しているのは、framework.dartで定義されている方のcontext
final store = Provider.of<MyStore>(context, listen: false);
store.addCounter();
}
それにより「ProviderNotFoundException
」が発生する。
そのため、引数でChangeNotifierProvider
のbuilder
プロパティのcontext
を渡す必要がある。
class _MyHomePageState extends State<MyHomePage> {
void _incrementCounter(BuildContext context) {
// 渡したのは、ChangeNotifierProviderのcontext
final store = Provider.of<MyStore>(context, listen: false);
store.addCounter();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => MyStore(counter: 0),
builder: (BuildContext context, _) { // ←ここのcontextを渡す
return Scaffold(
// 省略
floatingActionButton: FloatingActionButton(
// ChangeNotifierProviderのbuildプロパティのcontextを渡す
onPressed: () => _incrementCounter(context),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
},
);
}
}
おわりに
context
とofメソッドの仕様を把握していれば、より理解が深まる問題だと思う。
Flutterの初学者はRiverpodではなくProviderから始めることもあるようなので、そういう方たちの参考になれれば。
Discussion