継承と合成+インターフェースについて
継承とポリモーフィズムについて
インターフェースと合成
Riverpodの利点
基本的な流れは公式ドキュメントに従って行います。
【実装フロー】
①パッケージの追加
公式ドキュメントに従いパッケージを追加していきます。
riverpod_lint/custom_lintについてはさらに別の手順が必要なので注意。
flutter pub run build_runner watchによる自動生成も必要
拡張機能のFlutter Riverpod Snippetsがあるとショートカットが導入されます。
↓のサイトでショートカットキーを確認できます。
②ProviderScopeでアプリケーション全体をラップする
これによって、アプリケーション全体でRiverpodが有効になります。
③ProviderとModelの定義
プロバイダーは状態の一部をカプセル化して、その状態をlistenできるようにするオブジェクトです。
Providerと通常の関数の違い:
→ProviderはGET network requestsに最適です。
【Modelの定義】
Providerを作成する前にAPIから受け取るデータのモデルを定義する必要があります
このモデルには、JSONオブジェクトをDartのインスタンスに解析する方法も必要です。
JSONのデコード(解析)にはFreezedやjson_serializableパッケージなどのコードジェネレーターを使用することをオススメします。モデルを自動生成してくれます。
Dart Data Class拡張機能による自動生成も有用でfreezedより使い易い可能性もあります。
【Providerの定義・作成】
モデルを完成させたので、APIのクエリを開始できます。
Providerオブジェクトを作成するとUI内でProviderを使用できる用になります。
スニペットを使った場合
使わなかった場合
↑では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をサブクラス化する
これによりbuild()メソッドはWidgetRef型のrefオブジェクトを取得します。
→ウィジェット内でProviderの値を監視できる様になります。
Consumerを使用する
Consumerのbuilderの引数としてrefオブジェクトを渡します。
→Providerの値に変更があった場合、このbuilderのみが呼び出され再構築します。
Consumer()にラップされたウィジェットのみ再構築されます。
Providerをオブジェクト化した変数に依存しないウィジェットはラップしません。
StatefulWidgetの代わりにConsumerStatefulWidgetをサブクラス化する
WidgetRefとは:
WidgetRef型のrefオブジェクトを使用することでProviderの値を監視・アクセスできます。
ConsumerまたはConsumerWidgetを使用する場合は引数として、ConsumerStateからサブクラス化する場合はプロパティとして使用します。
よくまとめられている参考資料
【パッケージ追加~実装】
【名前付きルート(nameによる遷移)】
【pathParametersを使った遷移の管理】
【go()とpush()の使い分け】
【エラー処理】
goとpushの使い分けはルート階層を意識して行う必要があります。
goでは前のルートを破棄してターゲットルートに飛び、pushでは前のルートの上にターゲットルートを追加しナビゲーションスタックを維持します。
そのためpushで画面遷移を行った場合、前のページに戻ることができます。
一方、goではサブルートから上位のルートに戻ることになります。
pushは問題点が多いため出来る限りgoを使用することが推奨されます。
商品検索ページ→個別の商品ページ→カートと遷移する場合に
カートから個別の商品ページに戻りたい場合等にはpushを使った方が便利です。
参考資料
認識されないURLを開こうとする等エラーを出した場合に、エラーページのスクリーンウィジェットに飛ばす処理を行う。
errorBuilder:
GoRouterの中でerrorBuilderを指定してエラー処理を行うスクリーンに画面遷移させる
GoRouter(
/* ... */
errorBuilder: (context, state) => ErrorScreen(state.error),
);
【実装】
↓のページに飛ばす
まずbuilder関数に渡されるstateについて
builder()にはstateオブジェクトが渡されます。
stateオブジェクトはGoRouterStateクラスのインスタンスで、いくつかの有用な情報を含んでいます。
そのGoRouterStateクラスのプロパティ(情報)がpathやname、pathParameters、paramsなどです。
↓のサイトでプロパティの詳細を確認できます。
pathパラメーターの実装
①定義
定義時にpathセグメントで:の後に一意の名前を付けることで指定できます。
GoRoute(
name: 'product',
path: 'products/:productId',
builder: /* ... */,
),
1つの定義で複数のページに遷移できるため便利です。リスト化された複数の商品ページで遷移させる際等によく使います。
また、URLにもpathパラメーターの値が反映されます。
②呼び出し(画面遷移)
呼び出す時にpath若しくは名前付きpathに加えて、pathパラメーターの引数を渡す必要があります。
TextButton(
onPressed: () {
context.goNamed('product', pathParameters: {'productId': 123});
},
child: const Text('Go to product 123'),
),
上の例では、goNamed()に名前付きpathとpathParametersを渡して呼び出しています。
id等の商品情報をまとめたモデルクラスを作って管理すると楽になります。
おまけ
他のパラメーターについての参考資料
paramsの場合、パラメーターはナビゲーション時にキーと値のペアのマップとして渡します。
GoRouterはpathでは無くnameパラメータによって画面遷移させる事もできます。
列挙型でルート名を定義・管理することができるのでpathを引数として渡す遷移より管理がしやすいです。
呼び出す際に、pathによる場合は文字列リテラルを渡しますが、名前付きルートではハードコーディングする必要が無くなります。よって自動入力の提案を受ける事が出来るようになり入力ミスを減らせます。
【実装フロー】
①定義
②nameを使用して遷移 goNamed
参考資料