🐼

【Flutter】go_router をタイプセーフに使う方法【go_router_builder】

2022/08/02に公開

はじめに

Navigator 2.0 のデファクトスタンダードになっている go_router をより安全に利用できるパッケージ go_router_builder を紹介します!現在 go_router を使っていて、タイプセーフに書けないとお悩みの方にオススメです!

https://pub.dev/packages/go_router_builder

タイプセーフに画面遷移できる

go_router_builder 導入のモチベーションは、タイプセーフに画面遷移できることです。導入前後のコードを見比べてみてもらうのがわかりやすいと思います。

【導入前】 go_router の画面遷移方法
context.go(
  '/family/${family.id}/person/${person.id}/details?details=${entry.key.name}',
  extra: ++_extraClickCount,
);

// goNamed() の場合
context.goNamed(
  PersonDetailsPage.name,
  params: {
    'fid': family.id,
    'pid': person.id.toString(),
  },
  queryParams: {
    'details': entry.key.name,
  },
  extra: ++_extraClickCount,
);
【導入後】 go_router_builder の画面遷移方法
PersonDetailsRoute(
  family.id,
  person.id,
  details: entry.key.name,
  $extra: ++_extraClickCount,
).go(context);

導入前はパスやパラメータ名 ( 例:fid ) を文字列で扱っているため、タイポの危険がつきまといますが、導入後は名前付き引数で扱えるのでタイポを未然に防ぐことが出来ます!あと、値は String 以外の型も扱えるようになります!

それでは、go_router_builder の導入方法を見ていきましょう!

導入方法

環境

Flutter 3.0.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f1875d570e (3 weeks ago) • 2022-07-13 11:24:16 -0700
Engine • revision e85ea0e79c
Tools • Dart 2.17.6 • DevTools 2.12.2

pubspec.yaml を編集する

次のようにパッケージを追加します。build_runner はコードを自動生成するのに使います。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  go_router: ^4.2.2

dev_dependencies:
+  build_runner: ^2.2.0
  flutter_test:
    sdk: flutter
+  go_router_builder: ^1.0.7

自動生成コードのファイル名を追記する

GoRouter クラスを生成しているファイルの import 文の次の行に、次の 1 行を追記します。ここでは router.dart というファイル名を例にとって説明します。

router.dart
+ part 'router.g.dart';

ルートを定義する

go_router では次のように GoRouter クラスの routes プロパティにルートを定義していました。

go_router のルート定義
GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
    ),
  ],
  ・・・
);

go_router_builder では、次のように @TypedGoRoute アノテーションと GoRouteDate クラスを使ってルートを定義し直します。

go_router_builder のルート定義
<HomeRoute>(
  path: '/',
)
class HomeRoute extends GoRouteData {
  const HomeRoute();

  
  Widget build(BuildContext context) => const HomePage();
}

コードジェネレーターを実行する

次のコマンドを実行して自動生成ファイル ( 例: router.g.dart ) を生成します。

flutter pub run build_runner build --delete-conflicting-outputs

GoRouter の初期化

自動生成ファイルができたら、GoRouter クラスの routes プロパティを次のように書き換えます。これでルートの定義は完了です!

go_router_builder 導入後の GoRouter の初期化
GoRouter(
-  routes: [
-    GoRoute(
-      path: '/',
-      builder: (context, state) => const HomeScreen(),
-    ),
-  ],
+  routes: $appRoutes,
  ・・・
);

画面遷移

最後に、画面遷移の実装を次のように書き換えます。go_router では次のように画面遷移をしていました。

go_router の画面遷移の実装
context.go('/');

// goNamed() の場合
context.goNamed(HomePage.name);

go_router_builder では、次のように GoRouteData クラスを継承したクラスを使って画面遷移を実装します。

go_router_builder の画面遷移の実装
const HomeRoute().go(context);

パスパラメータ、クエリ、Extra の受け取り方法

go_router の時は次のように受け取っていました。

go_router のパスパラメータ、クエリ、Extra の受け取り例
GoRoute(
  path: 'details/:details',
  builder: (context, state) => PersonDetailsPage(
    fid: state.params['fid']!,
    pid: int.parse(state.params['pid']!),
    details: PersonDetails.valueOf(state.params['details']),
    extra: state.extra as int?,
  ),
);

go_router_builder でどのように受け取るのかを紹介します!

パスパラメータ

例えば、/family/:fidfid を受け取りたい場合は、名前無し、必須パラメータ にします。パスパラメータの型には、StringintdoubleboolEnum が指定できました(独自クラスは指定できませんでした)。

パスパラメータの受け取り例
class FamilyRoute extends GoRouteData {
  const FamilyRoute(
    this.fid,
  );

  final String fid;

  
  Widget build(BuildContext context) => FamilyPage(fid: fid);
}
パスパラメータの受け渡し例
FamilyRoute(family.id).go(context);

クエリ

例えば、/login?from=aaaaaaaaaa を受け取りたい場合は、名前付き、任意パラメータ にします。パスパラメータ同様にStringintdoubleboolEnum が指定できました。

クエリの受け取り例
<LoginRoute>(
  path: '/login',
)
class LoginRoute extends GoRouteData {
  const LoginRoute({
    this.from,
  });

  final String? from;

  
  Widget build(BuildContext context) => LoginPage(from: from);
}
クエリの受け渡し例
LoginRoute(from: state.subloc).go(context);

Extra

Extra を受け取りたい場合は、$extra という特別なパラメータ名の 名前付き、任意パラメータ にします。

Extra の受け取り例
class PersonDetailsRoute extends GoRouteData {
  const PersonDetailsRoute(
    this.fid,
    this.pid, {
    this.details,
    this.$extra,
  });

  final String fid;
  final int pid;
  final String? details;
  final int? $extra;

  
  Page<void> buildPage(BuildContext context) {
    return MaterialPage<Object>(
      fullscreenDialog: true,
      child: PersonDetailsPage(
        fid: fid,
        pid: pid,
        details: PersonDetails.valueOf(details),
        extra: $extra,
      ),
    );
  }
}
Extra の受け渡し例
PersonDetailsRoute(
  family.id,
  person.id,
  details: entry.key.name,
  $extra: ++_extraClickCount,
).go(context);

エラー画面、リダイレクト、トランジション

エラー画面、リダイレクト、トランジションなどの実装方法は README に書かれています。是非一読してみてください。

https://pub.dev/packages/go_router_builder

下記の公式サンプルコードもとても参考になりました。

https://github.com/flutter/packages/tree/main/packages/go_router_builder/example/lib

はまりポイント

複数の最上位ルートが自動生成されない場合

次のように複数の最上位ルートを定義するとき、@TypedGoRoute の定義のすぐ次に GoRouteData の定義を書かないと、 build_runner を実行したときに正しく自動生成されません。

ダメな例
<HomeRoute>(
  path: '/',
)
<LoginRoute>(
  path: '/login',
)
class HomeRoute extends GoRouteData {
  const HomeRoute();

  
  Widget build(BuildContext context) => const HomePage();
}

class LoginRoute extends GoRouteData {
  const LoginRoute({
    this.from,
  });

  final String? from;

  
  Widget build(BuildContext context) => LoginPage(from: from);
}
良い例
<HomeRoute>(
  path: '/',
)
class HomeRoute extends GoRouteData {
  const HomeRoute();

  
  Widget build(BuildContext context) => const HomePage();
}

<LoginRoute>(
  path: '/login',
)
class LoginRoute extends GoRouteData {
  const LoginRoute({
    this.from,
  });

  final String? from;

  
  Widget build(BuildContext context) => LoginPage(from: from);
}

まだまだ不安定

go_router_builder の Open Issues は 13 件あります(2022/08/02現在)。

https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+go_router_builder

特に次の Issue は、Extra を使うとその配下の画面を表示するとエラーが起こるという正常系バグです。go_router および go_router_builder はまだまだ発展途上のパッケージであり、このような正常系バグがまだまだ残っているフェーズであると認識した上で利用する必要がありそうです。

https://github.com/flutter/flutter/issues/106121

サンプルコードを公開しています

公式のサンプルコード をベースに go_router_builder を使ったサンプルコードを作ったので公開します。

https://github.com/susatthi/flutter-sample-go-router-builder

最後に

Flutter 大学という Flutter エンジニアに特化した学習コミュニティに所属しています。オンラインでわいわい議論したり、Flutter の最新情報をゲットしたりできます!ぜひ Flutter 界隈を盛り上げていきましょう!

https://flutteruniv.com?invite_id=9hsdZHg0qtaMIr6RPRulAaRJfA83

Discussion