Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

go_routerによる画面遷移時のパラメーターの渡し方

こんにちは。クライアントグループの上原です。 StudyplusのFlutterアプリでの開発には、go_routerが利用されています。 今回は、利用していて感じたどういう状況でどの方法を利用して画面遷移時にパラメーターを渡すのが良いのかを紹介します。 また、上記のやり方をgo_router_builderを利用して行った場合に、安全にデータをやり取りする方法も紹介します。

利用したFlutterのバージョンは、3.13.7です。 利用したライブラリのバージョンは、下記になります。

ライブラリ名 バージョン
go_router 12.0.1
go_router_builder 2.3.4

画面遷移時のパラメーターの渡し方について

go_routerでは、3つのやり方でパラメーターを次の画面へ渡すことができます。

パスパラメーター

パスの中に:パスパラメーター名といった形で指定できます。 stateのpathParameters(Map<String, String>)にパスパラメーター名を利用してアクセスすることでパスに渡ってきた各パラメーターを取得できます。

下記例では、/book/detail/1といったパスへアクセスが来た時にstate.pathParameters['bookId']により1が取得されBookScreenにbookId 1を設定した画面が表示されるようになります。

final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/book/detail/:bookId',
      builder: (BuildContext context, GoRouterState state) {
        return BookScreen(
          bookId: state.pathParameters['bookId'],
        );
      },
    ),
  ],
);

この渡し方を利用する場面としては、本が複数種類存在していてidなどで一意に定まるデータを取得する場面での利用が考えられます。

クエリパラメーター

state.uri.queryParameters(Map<String, String>)にクエリパラメーター名を利用してアクセスすることで渡ってきた各クエリパラメーターを取得できます。 下記例では、/book/search?query=title&order=descといったパスへアクセスが来た時にstate.uri.queryParameters['query']によりtitleが取得state.uri.queryParameters['order']によりdescが取得されBookSearchScreenにquery:title, order:descを設定した画面が表示されるようになります。

final _router = GoRouter(
  routes: [
    GoRoute(
      path: 'book/search',
      builder: (BuildContext context, GoRouterState state) {
        final query = state.uri.queryParameters['query'];
        final order = state.uri.queryParameters['order'];
        return BookSearchScreen(
          query: query,
          order: order,
        );
      },
    ),
  ],
);

この渡し方を利用する画面としては、上記の例のように、検索のオプションのようなデータを操作したものを取得したい場合に利用します。 クエリパラメーターの場合、クエリパラメーターが渡ってくるかどうかは分からないので基本的には、どれかが欠けていても問題ないようにすることが大事です。

Extraパラメーター

stateのextra(Object?)を利用しオブジェクトを渡すことでデータを取得できます。 下記例では、_tapメソッドを叩くとBookオブジェクトを渡して/bookへ遷移します。 /bookパスへアクセスが来た時にstate.extraをBookにキャストしてBookScreenに設定しています。

void _tap() => context.go(
      '/book',
      extra: Book(
        id: 1,
        title: 'タイトル',
      ),
    );

final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/book',
      builder: (BuildContext context, GoRouterState state) => BookScreen(
        book: state.extra! as Book,
      ),
    ),
  ],
);

Extraパラメーターを利用すると様々な状況で便利にデータを渡すことが可能になります。 例えば、本の一覧画面から本のオブジェクトを本の詳細画面へ渡すことで、パスパラメーターでは、本のidを利用して本を取得していた箇所がオブジェクトをそのまま渡す形で置き換えることも出来るようになります。 しかし、便利な反面、オブジェクトを渡すことになるのでidを渡していた時にはなかった問題もあります。 遷移元がオブジェクトをきちんと保持していることが前提なので、ディープリンクなどの直接的な画面遷移には対応できません。 またWebでの利用を考えると、戻るボタンを押して前画面に戻った時や、リロードやURLの直打ちといった事例の際にオブジェクトを利用できない問題などもあります。 Studyplusでは、今後WebでFlutterを利用予定のためExtraパラメーターを利用せず、パスパラメーター、クエリパラメーターを利用しての画面遷移を行なっています。

go_router_builderを利用した型安全なパラメーターの渡し方

3つのパラメーターの渡し方を紹介しましたが、パスパラメーター・クエリパラメーターはStringでデータが渡され、ExtraパラメーターはObjectでデータが渡される形になっています。 しかし、上記の渡し方では、本来渡したいはずの型情報が抜け落ちており安全にパラメーターを渡して画面遷移を実現出来ているとは言えません。 安全にパラメーターを渡せていないとどうなるかというと下記コードを見てください。

GoRoute(
  path: '/book/detail/:bookId',
  builder: (BuildContext context, GoRouterState state) {
    final bookId = int.parse(state.pathParameters['bookId']!);
    return BookScreen(
      bookId: bookId,
    );
  },
),

この定義では、/book/detail/:bookId:bookIdがintを要求している状況です。 しかし、実際には、context.go('/book/detail/id1');のようなコードが書けてしまいます。このコードを動かすとStringが渡りそれをintにパースしようとして失敗してしまうことになります。それを防ぐために、go_router_builderを利用します。

事前準備

Routeを定義するファイルでgo_router_builderで生成されるファイルを利用するためにpartで参照します。

import 'package:go_router/go_router.dart';

part 'this_file.g.dart';

GoRouterの定義部分のroutesを$appRoutesに変更します。

final GoRouter router = GoRouter(
      debugLogDiagnostics: true,
      routes: $appRoutes,
    );

TypedGoRouteの定義

それでは、実際にRouteの定義をしていきます。/配下にbook/detail/:bookIdを設定してTypedGoRouteBookDetailRouteを設定します。

@TypedGoRoute<HomeRoute>(
  path: '/',
  routes: <TypedGoRoute<GoRouteData>>[
    TypedGoRoute<BookDetailRoute>(
      path: 'book/detail/:bookId',
    ),
  ],
)
// HomeRouteの定義は省いています。
class BookDetailRoute extends GoRouteData {
  const BookDetailRoute({
    required this.bookId,
  });

  final int bookId;

  @override
  Widget build(BuildContext context, GoRouterState state) => BookScreen(
        bookId: bookId,
      );
}

上記処理を書いた後にbuild_runnerを走らせます。 画面遷移をしたい時は下記のように、BookDetailRouteを作成しpushなどの遷移をします。

onTap: () => const BookDetailRoute(bookId: 1).push(context)

go_router_builderを利用することで、安全に画面遷移時にパラメータを渡すことが出来るようになりました。 また、画面遷移時のパス設定の間違えもBookDetailRouteを他の場所でも使うことができるおかげで少なくできます。

まとめ

go_routerを利用した画面遷移時のパラメーターの渡し方について、どういう渡し方があるのか、どういう場面で利用するべきなのかを整理できました。また、go_router_builderを利用することで安全にパラメーターを渡すことができ、開発時のミスを防ぐことが出来るようになりました。