👙

GoRouterのパスパラメーターと詳細ページへ値を渡す方法

2023/12/01に公開

パラメーターってそもそもなんなのか?

https://pub.dev/documentation/go_router/latest/topics/Configuration-topic.html

パスパラメータとクエリパラメータは、URLを通じてサーバーに情報を送信するための2つの方法です。

パスパラメータ: URLの一部として送信されます。例えば、https://example.com/users/123の123はパスパラメータです。この場合、123は特定のユーザーのIDを表しています。パスパラメータは、リソースの特定のインスタンスやアイテムを識別するのによく使われます。

クエリパラメータ: URLの末尾に?記号の後に追加され、key=valueの形式で表されます。例えば、https://example.com/users?sort=descのsort=descはクエリパラメータです。この場合、sort=descはユーザーを降順でソートすることを指示しています。クエリパラメータは、リソースのフィルタリングやソート、ページネーションなどによく使われます。

これらのパラメータは、HTTPリクエストの一部としてサーバーに送信され、サーバーはこれらのパラメータを使用してリクエストを処理します。

🧑‍🎓こちらに実験用のサンプルがございます

https://github.com/sakurakotubaki/SupabaseExample

パスの定数

Dart3のfinalkeywordをつけると、継承できないクラスになる。これでどこかで継承されて、中のコードに変更が加えられることはなくなる。

final class RouterPath {
  static const HOME = '/';
  static const DETAIL = 'detail';
  static const PATH_PARAM = 'path_param';
}
ルートの定義

RouterPathクラスのインスタンス化しないで使える変数を使いルートにパスを指定する。パスパラメーターのところは書き方がちょっと違います。

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:page_builder_ui/common/router_path.dart';
import 'package:page_builder_ui/view/category.dart';
import 'package:page_builder_ui/view/detail_page.dart';
import 'package:page_builder_ui/view/parameter_test.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../domain/entity/category.dart';

part 'router.g.dart';


GoRouter router(RouterRef ref) {
  return GoRouter(
      routes: [
        GoRoute(
          path: RouterPath.HOME,
          name: RouterPath.HOME,
          builder: (context, state) => const CategoryPage(),
          routes: [
            // 詳細ページへのルーティング
            GoRoute(
              path: RouterPath.DETAIL,
              name: RouterPath.DETAIL,
              builder: (context, state) {
                final category = state.extra as CategoryModel;
                return DetailPage(category);
              },
            ),
            // パスパラメーターのテスト
            GoRoute(
              path: '${RouterPath.PATH_PARAM}/:id',
              name: RouterPath.PATH_PARAM,
              builder: (context, state) {
                final id = state.pathParameters['id']!;
                return ParameterTest(id: id);
              },
            ),
          ]
        ),
      ],
      // 404ページを指定
      errorPageBuilder: (context, state) {
        return const MaterialPage(
            child: Scaffold(
          body: Center(
            child: Text('Page not found'),
          ),
        ));
      });
}
モデルの値を渡す例

domainとなるentity(モデルって言った方がわかりやすいですね)

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'category.freezed.dart';
part 'category.g.dart';


class CategoryModel with _$CategoryModel {
  const factory CategoryModel({
    (0) int id,
    ('') String name,
  }) = _CategoryModel;

  factory CategoryModel.fromJson(Map<String, dynamic> json) =>
      _$CategoryModelFromJson(json);
}

entityから値を受け取るページ

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:page_builder_ui/domain/entity/category.dart';

class DetailPage extends ConsumerWidget {
  const DetailPage(this.category, {super.key});

  final CategoryModel category;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: Text(category.id.toString()),
      ),
      body: Center(
        child: Text(category.name),
      ),
    );
  }
}

entityの値を渡すページ

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:page_builder_ui/common/router_path.dart';
import 'package:page_builder_ui/provider/category_provider.dart';

class CategoryPage extends ConsumerWidget {
  const CategoryPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    final categories = ref.watch(categoryListStreamProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('categories'),
      ),
      body: categories.when(
          data: (data) {
            return ListView.builder(
              itemCount: data.length,
              itemBuilder: (context, index) {
                return ListTile(
                  onTap: () {
                    context.goNamed(RouterPath.DETAIL, extra: data[index]);
                  },
                  title: Text(data[index].name),
                );
              },
            );
          },
          error: (e, s) => Center(child: Text(e.toString())),
          loading: () => const Center(child: CircularProgressIndicator())),
    );
  }
}

こんな感じです
https://youtube.com/shorts/DryzGMCmir4?feature=share

パスでidを渡す場合

idを受け取れるように引数が必須のコンストラクターを定義しておく。後は、前のページからパスを渡すだけ。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class ParameterTest extends ConsumerWidget {
  const ParameterTest({Key? key, required this.id}) : super(key: key);

  final String id;

  
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('パスパラメーターのテスト'),
      ),
      body: Center(
        child: Text('idは: $id'),
      ),
    );
  }
}

idを渡せるように、前のページを修正

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:page_builder_ui/common/router_path.dart';
import 'package:page_builder_ui/provider/category_provider.dart';

class CategoryPage extends ConsumerWidget {
  const CategoryPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    final categories = ref.watch(categoryListStreamProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text('categories'),
      ),
      body: categories.when(
          data: (data) {
            return ListView.builder(
              itemCount: data.length,
              itemBuilder: (context, index) {
                return ListTile(
                  onTap: () {
                    context.goNamed(RouterPath.PATH_PARAM,
                        pathParameters: {'id': data[index].id.toString()});
                  },
                  title: Text(data[index].name),
                );
              },
            );
          },
          error: (e, s) => Center(child: Text(e.toString())),
          loading: () => const Center(child: CircularProgressIndicator())),
    );
  }
}

こんな感じです
https://youtube.com/shorts/YLiT3bkK4VM?feature=share

プロバイダー

Supabase使ってるので、プロバイダーで呼び出してます。詳しくはGithubのリポジトリを見て色々試して欲しいです。

import 'package:page_builder_ui/domain/entity/category.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'category_provider.g.dart';

/// TODO: Providerが使えない?
final supabaseInstance = Supabase.instance.client;

/// [全てのデータを取得する]
// final categoryStreamProvider =
//     StreamProvider.autoDispose<List<Map<String, dynamic>>>((ref) {
//   final supabase = Supabase.instance.client;
//   return supabase.from('categories').stream(primaryKey: ['id']);
// });

/// [モデルクラスを使用してデータを取得する]
// final categoryListStreamProvider =
//     StreamProvider.autoDispose<List<Category>>((ref) {
//   final supabase = Supabase.instance.client;
//   return supabase.from('categories').stream(primaryKey: ['id']).map((event) => event.map((e) => Category.fromJson(e)).toList());
// });

// 自動生成するコマンド
// flutter pub run build_runner watch --delete-conflicting-outputs

/// [ジェネレーターを使用してデータを取得する]

Stream<List<Map<String, dynamic>>> categoryStream(CategoryStreamRef ref) {
  return supabaseInstance.from('categories').stream(primaryKey: ['id']);
}

/// [モデルクラスを使用して、ジェネレーターでデータを取得する]

Stream<List<CategoryModel>> categoryListStream(CategoryListStreamRef ref) {
      return supabaseInstance.from('categories').stream(primaryKey: ['id']).map(
          (event) => event.map((e) => CategoryModel.fromJson(e)).toList());
}

最後に

今回は、普段使わないGoRouterのパスパラメーターなるものを使ってみました。過去に一応記事は書いていたみたいなんですけどね。

https://zenn.dev/joo_hashi/articles/4387d0e7aa51ef
https://zenn.dev/joo_hashi/articles/5bf8d16df7f187

一応Supabaseに貢献してる人なんで、Zennで本を出してます。
https://zenn.dev/joo_hashi/books/a5e2247b85dc0a

Jboy王国メディア

Discussion