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 つのプロバイダーを作成し、オーバーライドを使用するという複雑さは、手間に見合いません。