サービス開発パートナー事業部 アプリエンジニアチーム 自称Flutterエンジニアの務台です。最近ジムに通い始めました。弊社はフルリモート勤務なので意識して運動していきたいところです。
Flutter開発において、画面遷移にはgo_routerを使うのが主流かと思います。
かくいう私もgo_routerをよく使っていたのですが、
- パスパラメータに文字列型しか使えない
- extraで渡した値が
Object?
になってしまう
のがかなり使いづらいなぁと感じていました。
そこで今更ではありますが、その辺りを解決してくれるgo_router_builderを使ってみました。
バージョン
go_router: 6.5.7 build_runner: 2.2.1 build_verify: 3.1.0 go_router_builder: ^1.2.1
つかいかた
パッケージのインポート
まずはpubspec.yaml
に使用するパッケージを追加して
dependencies: go_router: ^6.5.7 dev_dependencies: build_runner: ^2.2.1 build_verify: ^3.1.0 go_router_builder: ^1.2.1
パッケージをインポートします。
flutter pub get
ルーティングの定義
まずはルーティングを定義するためのファイルを作成します。
go_router_builder
では自動生成されるファイルを使用するので、part
宣言もしておきます。
app_route.dart
part 'app_route.g.dart';
続いてページの宣言を追記していきます。
この時、ページに引数が必要な場合はルートクラスの引数として渡してあげることで、実際の遷移を実装する際にタイプセーフにすることができるようになります。
app_route.dart
// 引数が不要な遷移 @immutable class HomeScreenRoute extends GoRouteData { @override Widget build(BuildContext context, GoRouterState state) { return const HomeScreenView(); } } // 引数が不要な遷移 @immutable class UserListRoute extends GoRouteData { @override Widget build(BuildContext context, GoRouterState state) { return const UserListView(); } } // 引数が必要な遷移 @immutable class UserDetailRoute extends GoRouteData { const UserDetailRoute({ required this.id, }); final int id; @override Widget build(BuildContext context, GoRouterState state) { return UserDetailView(userId: id); } }
また、extra
を使用する場合は指定の$extra
という変数名で宣言する必要があります。
// extraとして引数を渡す遷移 @immutable class UserDetailRoute extends GoRouteData { const UserDetailRoute({ this.$extra, }); final UserDetailRoutingParam? $extra; @override Widget build(BuildContext context, GoRouterState state) { return UserDetailView(userParam: $extra!); } }
$extra
を使うことで任意の値を渡すことができるようになりますが、null許容型にはなってしまうため、個人的にはできるだけ使わない方が良いのかなと思いました。
続いて同ファイルにルーティングを追記します。
ここで先ほど作成したルート宣言とパスを紐付けます。 通常のgo_routerと同様パスパラメータも使用することができ、ルート宣言したクラスの引数として渡した値が自動で置換されてパスパラメータとして使用されます。
app_route.dart
@TypedGoRoute<HomeScreenRoute>( path: '/', routes: [ TypedGoRoute<UserListRoute>( path: 'user', routes: [ TypedGoRoute<UserDetailRoute>(path: ':id/detail'), // [:id]の部分が引数のidの値に置換される ], ), ], )
その後自動生成ファイルを作成します。
flutter pub run build_runner build
最後に設定したルーティング情報をGoRouter
へ渡します。この時渡す参照は自分で作成したものでなく、自動生成された$appRoutes
を使用します。
app_router.dart
class AppRouter extends StatelessWidget { AppRouter({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return MaterialApp.router( routerConfig: _router, ); } final GoRouter _router = GoRouter(routes: $appRoutes); }
ルーティングの設定は以上です。
画面遷移
前節で定義したルーティング情報を使ってタイプセーフに画面遷移を行います。
といっても特段難しいことはなく、ルート宣言で定義したクラスをインスタンス化し、go
、push
等で遷移すればOKです。
// UserListViewへ遷移 onPressed: () => const UserListRoute().go(context) // UserDetailViewへ遷移 onPressed: () => const UserDetailRoute(id: 100).push(context) // extraを使用してUsreDetailViewへ遷移 onPressed: () => const UserDetailRoute( $extra: UserDetailRoutingParam( id: 200, name: 'ユーザ名', ), ).pushReplacement(context)
これでgo_builder_routerを利用して、タイプセーフな画面遷移を行うことができるようになりました!
まとめ
go_router_builderを使用していないと、以下のような遷移を実装する必要があります。
// UserListViewへ遷移 onPressed: () => context.go('/user') // UserDetailViewへ遷移 onPressed: () => context.go('/user/100/detail') // extraを使用してUsreDetailViewへ遷移 onPressed: () => context.pushReplacement( extra: const UserDetailRoutingParam( id: 200, name: 'ユーザ名', ) )
ページへの遷移を都度ハードコードする必要があったりするので、
- ページへのパス合ってるんだっけ…
- 渡す引数あってるんだっけ…
といった実行時エラー系の心配をする必要がなくなります。
まだgo_router_builderを使用していない場合や、新しくアプリを作る時にはドンドン導入していきましょう!
全体像
最後に今回作成したルーティング用のファイルの全体像を掲載します。
app_route.dart
/// /// importは省略 /// part 'app_route.g.dart'; @TypedGoRoute<HomeScreenRoute>( path: '/', routes: [ TypedGoRoute<UserListRoute>( path: 'user', routes: [ TypedGoRoute<UserDetailRoute>(path: ':id/detail'), // [:id]の部分が引数のidの値に置換される ], ), ], ) // 引数が不要な遷移 @immutable class HomeScreenRoute extends GoRouteData { @override Widget build(BuildContext context, GoRouterState state) { return const HomeScreenView(); } } // 引数が不要な遷移 @immutable class UserListRoute extends GoRouteData { @override Widget build(BuildContext context, GoRouterState state) { return const UserListView(); } } // 引数が必要な遷移 @immutable class UserDetailRoute extends GoRouteData { const UserDetailRoute({ required this.id, }); final int id; @override Widget build(BuildContext context, GoRouterState state) { return UserDetailView(userId: id); } } // // extraとして引数を渡す遷移 // @immutable // class UserDetailRoute extends GoRouteData { // const UserDetailRoute({ // this.$extra, // }); // final UserDetailRoutingParam? $extra; // @override // Widget build(BuildContext context, GoRouterState state) { // return UserDetailView(userParam: $extra!); // } // }
app_router.dart
/// /// importは省略 /// class AppRouter extends StatelessWidget { AppRouter({super.key}); @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: _router, ); } final GoRouter _router = GoRouter(routes: $appRoutes); }