アプリ開発・ゲーム開発のための勉強ブログ

FlutterとUnrealEngineの勉強をしています。

【Flutter】Riverpodの実装 箇条書き

Riverpodの利点

 

基本的な流れは公式ドキュメントに従って行います。

 

 

【実装フロー】

①パッケージの追加

公式ドキュメントに従いパッケージを追加していきます。

riverpod_lint/custom_lintについてはさらに別の手順が必要なので注意。

flutter pub run build_runner watchによる自動生成も必要

 

拡張機能のFlutter Riverpod Snippetsがあるとショートカットが導入されます。

↓のサイトでショートカットキーを確認できます。

 

 

②ProviderScopeでアプリケーション全体をラップする

  1. void main() {
  2.   runApp(
  3.     ProviderScope(
  4.       child: MyApp(),
  5.     ),
  6.   );
  7. }

ウィジェットツリーのルートウィジェットをラップします。

これによって、アプリケーション全体でRiverpodが有効になります。

 

③ProviderとModelの定義

プロバイダーは状態の一部をカプセル化して、その状態をlistenできるようにするオブジェクトです。

Providerと通常の関数の違い:

  • キャッシュされる
  • error/loadingの処理が提供される
  • listenし続ける
  • データが変更された時に自動的に再実行される

→ProviderはGET network requestsに最適です。

 

【Modelの定義】

Providerを作成する前にAPIから受け取るデータのモデルを定義する必要があります

このモデルには、JSONオブジェクトをDartインスタンスに解析する方法も必要です。

JSONのデコード(解析)にはFreezedやjson_serializableパッケージなどのコードジェネレーターを使用することをオススメします。モデルを自動生成してくれます。

 

Dart Data Class拡張機能による自動生成も有用でfreezedより使い易い可能性もあります。

 

【Providerの定義・作成】

モデルを完成させたので、APIのクエリを開始できます。

Providerオブジェクトを作成するとUI内でProviderを使用できる用になります。

 

スニペットを使った場合

  1. @riverpod
  2. String helloWorld(HelloWorldRef ref) {
  3.   return 'Hello world';
  4. }

使わなかった場合

  1. final helloWorldProvider = Provider<String>((ref) {
  2.   return 'Hello world';
  3. });

↑ではProviderの状態を読み取るためのグローバル変数を宣言しています。

 

Providerの定義にコード生成を使う場合は、上記の様に構文がわずかに変わります。

公式ではコード生成を使用することが推奨されています。 ※build_runnerが必要です。

↓にコード生成に使う定義が載っています。

 

Providerの種類については別の記事で細かくまとめますが、用途に合わせて6つのProviderから選択し使用します。(他にもProviderは存在しますが使いません)

選択は2つの軸から考えます。

1つ目の軸は値・将来の値・streamな値の内どれを保持するか(状態の種類)

2つ目の軸は通知(Notifier)を介してAPIによる状態の更新が必要かどうかAPIの変化)

 

初期化時の状態を扱う場合:

Provider、FutureProvider、StreamProvider

 

APIによる状態の更新が必要な場合:

Notifier、AsyncNotifier、StreamNotifier

 

 

④取得したProviderの使用方法

Providerはウィジェットツリーの外部に存在するため読み取るためにはrefオブジェクトが必要になります。その入手方法は3つです。

 

StatelessWidgetの代わりにConsumerWidgetをサブクラス化する

  1. final helloWorldProvider = Provider<String>((_) => 'Hello world');
  2.  
  3. class HelloWorldWidget extends ConsumerWidget {
  4.   @override
  5.   Widget build(BuildContext context, WidgetRef ref) {
  6.     final helloWorld = ref.watch(helloWorldProvider);
  7.     return Text(helloWorld);
  8.   }
  9. }

これによりbuild()メソッドはWidgetRef型のrefオブジェクトを取得します。

ウィジェット内でProviderの値を監視できる様になります。

 

Consumerを使用する

  1. final helloWorldProvider = Provider<String>((_) => 'Hello world');
  2.  
  3. class HelloWorldWidget extends StatelessWidget {
  4.   @override
  5.   Widget build(BuildContext context) {
  6.     return Consumer(
  7.       builder: (_, WidgetRef ref, __) {
  8.         final helloWorld = ref.watch(helloWorldProvider);
  9.         return Text(helloWorld);
  10.       },
  11.     );
  12.   }
  13. }

Consumerのbuilderの引数としてrefオブジェクトを渡します。

→Providerの値に変更があった場合、このbuilderのみが呼び出され再構築します。

Consumer()にラップされたウィジェットのみ再構築されます。

Providerをオブジェクト化した変数に依存しないウィジェットはラップしません。

 

StatefulWidgetの代わりにConsumerStatefulWidgetをサブクラス化する

 

  1. class HelloWorldWidget extends ConsumerStatefulWidget {
  2.   @override
  3.   ConsumerState<HelloWorldWidget> createState() => _HelloWorldWidgetState();
  4. }
  5.  
  6. class _HelloWorldWidgetState extends ConsumerState<HelloWorldWidget> {
  7.   @override
  8.   void initState() {
  9.     super.initState();
  10.  
  11.     final helloWorld = ref.read(helloWorldProvider);
  12.     print(helloWorld);
  13.   }
  14.  
  15.   @override
  16.   Widget build(BuildContext context) {
  17.     final helloWorld = ref.watch(helloWorldProvider);
  18.     return Text(helloWorld);
  19.   }
  20. }

 

 

WidgetRefとは:

WidgetRef型のrefオブジェクトを使用することでProviderの値を監視・アクセスできます。

ConsumerまたはConsumerWidgetを使用する場合は引数として、ConsumerStateからサブクラス化する場合はプロパティとして使用します。

 

 

よくまとめられている参考資料