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

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からサブクラス化する場合はプロパティとして使用します。

 

 

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

【Flutter】GoRouterの記事まとめ ※追記予定

【パッケージ追加~実装】

zizyotol.hatenablog.com

 

【名前付きルート(nameによる遷移)】

zizyotol.hatenablog.com

 

pathParametersを使った遷移の管理

zizyotol.hatenablog.com

 

【go()とpush()の使い分け】

zizyotol.hatenablog.com

 

【エラー処理】

zizyotol.hatenablog.com

【Flutter】GoRouter go()とpush()の使い分け

goとpushの使い分けはルート階層を意識して行う必要があります。

goでは前のルートを破棄してターゲットルートに飛び、pushでは前のルートの上にターゲットルートを追加しナビゲーションスタックを維持します。

そのためpushで画面遷移を行った場合、前のページに戻ることができます。

https://codewithandrea.com/articles/flutter-navigation-gorouter-go-vs-push/images/nav-stack.webp

 

一方、goではサブルートから上位のルートに戻ることになります。

pushは問題点が多いため出来る限りgoを使用することが推奨されます。

 

商品検索ページ→個別の商品ページ→カートと遷移する場合に

カートから個別の商品ページに戻りたい場合等にはpushを使った方が便利です。

 

 

参考資料

codewithandrea.com

pub.dev

docs.page

【Flutter】GoRouterでのエラー処理

認識されないURLを開こうとする等エラーを出した場合に、エラーページのスクリーンウィジェットに飛ばす処理を行う。

 

errorBuilder:

GoRouterの中でerrorBuilderを指定してエラー処理を行うスクリーンに画面遷移させる

GoRouter(
  /* ... */
  errorBuilder: (context, state) => ErrorScreen(state.error),
);


【実装】

  1. errorBuilder: (context, state) => const NotFoundScreen(),

↓のページに飛ばす

 

  1. class NotFoundScreen extends StatelessWidget {
  2.   const NotFoundScreen({super.key});
  3.  
  4.   @override
  5.   Widget build(BuildContext context) {
  6.     return Scaffold(
  7.       appBar: AppBar(),
  8.       body: Center(
  9.         child: Column(
  10.           children: [
  11.             const Text('404 - Page not found'),
  12.             TextButton(
  13.                 onPressed: () => context.goNamed(AppRoute.home.name),
  14.                 child: const Text("Go Home")),
  15.           ],
  16.         ),
  17.       ),
  18.     );
  19.   }
  20. }



 

【Flutter】GoRouter パスパラメーターを使って画面遷移を管理

まず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等の商品情報をまとめたモデルクラスを作って管理すると楽になります。

 

pub.dev

 

 

おまけ

他のパラメーターについての参考資料

paramsの場合、パラメーターはナビゲーション時にキーと値のペアのマップとして渡します。

 

【Flutter】GoRouter 名前付きルート

GoRouterはpathでは無くnameパラメータによって画面遷移させる事もできます。

列挙型でルート名を定義・管理することができるのでpathを引数として渡す遷移より管理がしやすいです。

呼び出す際に、pathによる場合は文字列リテラルを渡しますが、名前付きルートではハードコーディングする必要が無くなります。よって自動入力の提案を受ける事が出来るようになり入力ミスを減らせます。

 

【実装フロー】

①定義

  1. enum AppRoute {
  2.   home,
  3.   detail,
  4. }
  5.  
  6. final GoRouter _router = GoRouter(
  7.   routes: <RouteBase>[
  8.     GoRoute(
  9.       path: '/',
  10.       name: AppRoute.home.name,
  11.       builder: (BuildContext context, GoRouterState state) {
  12.         return const HomeScreen();
  13.       },
  14.       routes: <RouteBase>[
  15.         GoRoute(
  16.           path: 'details',
  17.           name: AppRoute.detail.name,
  18.           builder: (BuildContext context, GoRouterState state) {
  19.             return const DetailsScreen();
  20.           },
  21.         ),
  22.       ],
  23.     ),
  24.   ],
  25. );

 

②nameを使用して遷移 goNamed

  1. ElevatedButton(
  2.           onPressed: () => context.goNamed(AppRoute.detail.name),
  3.           child: const Text('Go to the Details screen'),
  4.         ),

 

  1. ElevatedButton(
  2.           onPressed: () => context.goNamed(AppRoute.home.name),
  3.           child: const Text('Go back to the Home screen'),
  4.         ),

 

参考資料

pub.dev