メインコンテンツに進む

providerの早期初期化

全ての provider はデフォルトでは遅延初期化されます。
これは、provider が初めて使用されるときに初期化されることを意味します。
これは、アプリケーションの特定の部分でのみ使用される provider に便利です。

残念ながら、Dart の動作(tree shaking のため)により、provider を早期に初期化する必要があるとフラグを立てる方法はありません。
ただし、アプリケーションの root で早期初期化したい provider を強制的に読み取る方法があります。

おすすめのアプローチは、ProviderScopeの直下に配置された Consumer で provider を単にwatchすることです:

void main() {
runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {

Widget build(BuildContext context) {
return const _EagerInitialization(
// TODO: アプリをレンダリングする。
child: MaterialApp(),
);
}
}

class _EagerInitialization extends ConsumerWidget {
const _EagerInitialization({required this.child});
final Widget child;


Widget build(BuildContext context, WidgetRef ref) {
// providerをwatchすることで早期初期化する。
// "watch"を使うことでproviderが維持され、破棄されません。
ref.watch(myProvider);
return child;
}
}
注記

初期化用の Consumer を”MyApp”やパブリックウィジェットに配置することを検討してください。
これにより、main からロジックを削除し、テストでも同じ動作を使用できるようになります。

FAQ

provider が変更されたときにアプリケーション全体が再構築されるのではないですか?

いいえ、そうではありません。
上記のサンプルでは、早期初期化を行う Consumer が別のウィジェットとして、childを返す以外に何もしません。

MaterialApp自体をインスタンス化するのではなく、childを返すことが重要です。
つまり、_EagerInitializationが再構築されても、child変数は変更されません。
そして、ウィジェットが変更されない場合、Flutter はウィジェットを再構築しません。

したがって、別のウィジェットがその provider をリッスンしていない限り、_EagerInitialization のみが再構築されます。

このアプローチを使用して、ローディング状態やエラー状態をどのように処理できますか?

Consumerで通常処理するのと同じように、ローディング/エラー状態を処理できます。
_EagerInitializationは provider が "loading" 状態にあるかどうかを確認し、そうであれば childの代わりに CircularProgressIndicatorを返すことができます:

class _EagerInitialization extends ConsumerWidget {
const _EagerInitialization({required this.child});
final Widget child;


Widget build(BuildContext context, WidgetRef ref) {
final result = ref.watch(myProvider);

// エラー状態とローディング状態を処理します。
if (result.isLoading) {
return const CircularProgressIndicator();
} else if (result.hasError) {
return const Text('Oopsy!');
}

return child;
}
}

ローディング/エラー状態を処理しましたが、他の Consumers も AsyncValue を受け取ります!すべてのウィジェットでローディング/エラー状態を処理しなくても済む方法はありますか?

provider がAsyncValueを公開しないようにする代わりに、ウィジェットがAsyncValue.requireValueを使用することができます。
これにより、パターンマッチングを行わずにデータを読み取ることができます。
そして、バグが発生した場合は明確なメッセージとともに例外がスローされます。

// 早期初期化される provider

Future<String> example(Ref ref) async => 'Hello world';

class MyConsumer extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
final result = ref.watch(exampleProvider);

/// providerが正しく早期初期化されれば "requireValue"でデータを直接読み込むことができます。
return Text(result.requireValue);
}
}
注記

これらの場合(スコープに依存する)にローディング/エラー状態を公開しない方法もありますが、一般的にはそれは推奨されません。
2 つのプロバイダーを作成し、オーバーライドを使用するという複雑さは、手間に見合いません。