タクシーアプリ『GO』の iOS アプリを開発している井戸田です。
今回はFlutterのgo_routerライブラリをもっと便利に使うことができるgo_router_builderというライブラリについて紹介します。
そもそもgo_routerとは
go_routerはFlutter公式で出している、Navigator2.0を簡単に扱えることができるライブラリです。 URLベースで遷移したい画面を指定することができます。
https://pub.dev/packages/go_router
go_routerの全体のコード
ルーティングの設定部分と遷移部分の全体のコードは下記のようになるかと思います。
ルーティングの設定部分
final goRouter = GoRouter( initialLocation: '/', routes: [ GoRoute( name: 'first', path: '/', builder: (context, state) => const FirstPage(), routes: [ GoRoute( name: 'second', path: 'second/:secondId', builder: (context, state) => SecondPgae( secondId: int.parse(state.pathParameters['secondId']!), ), routes: [ GoRoute( name: 'third', path: 'third', builder: (context, state) => ThirdPage( secondId: int.parse(state.pathParameters['secondId']!), filter: state.queryParameters['filter']!, ), ), ], ), ], ), ], );
遷移部分
// goの場合 onPressed: () => context.go('/second/1/third?filter=test') // goNamedの場合 onPressed: () => context.goNamed( 'third', pathParameter: <String, String>{ 'secondId': '1' }, queryParameter: <String, String>{ 'filter': 'test' } )
go_routerのつらいところ
go_routerのつらいところの1つとして全てStringで制御しないといけないところだと思います。
1. 自分で定数化する必要がある
name(third)の部分はStringのままでもいいですが定数化したくなると思います。
Before
context.goNamed( 'third', // ... )
After
class ThirdPage extend StatelessWidget { static const routeName = 'third'; } context.goNamed( ThirdPage.routeName, // ... )
2. pathParametersとqueryParametersのKeyがString
'secondId'や’filter’ はStringで指定しないといけないためtypoしてても気付きにくいです。
GoRoute( name: 'third', path: 'third', builder: (context, state) => ThirdPage( secondId: int.parse(state.pathParameters['secondId']!), filter: state.queryParameters['filter'], ), ), context.goNamed( 'third', pathParameters: <String, String>{ 'secondId': '1' }, queryParameters: <String, String>{ 'filter': 'test' } )
3. pathParametersとqueryParametersのvalueはString
pathParametersやqueryParametersのvalueはStringのため、それぞれのページで定義した型に自ら変換する必要があります。
GoRoute( name: 'third', path: 'third', builder: (context, state) => ThirdPage( secondId: int.parse(state.pathParameters['secondId']!), filter: state.queryParameters['filter'], ), ),
それらを解決できるのがgo_router_builderです!
go_router_builderとは
go_routerの拡張ライブラリで、Flutter公式のライブラリです。
go_routerの機能更新に追従してgo_router_builderでもできるようになっているため、基本的にgo_routerでできることはgo_router_builderでもできます。
ファイルを自動生成するためbuild_runnerが必要になります。
https://pub.dev/packages/go_router_builder
go_router_builderの使い方
全体のコード
ルーティング設定部分
part 'go_router_builder.g.dart'; final goRouterBuilder = GoRouter(routes: $appRoutes); @TypedGoRoute<FirstPageRoute>( path: '/', routes: [ TypedGoRoute<SecondPageRoute>( path: 'second/:secondId', routes: [ TypedGoRoute<ThirdPageRoute>(path: 'third'), ], ), ], ) class FirstPageRoute extends GoRouteData { const FirstPageRoute(); @override Widget build(BuildContext context, GoRouterState state) { return const FirstPage(); } } // … class ThirdPageRoute extends GoRouteData { ThirdPageRoute({required this.secondId, this.filter}); final int secondId; final String? filter; @override Widget build(BuildContext context, GoRouterState state) { return ThirdPage(secondId: secondId, filter: filter); } }
遷移部分
onPressed: () => ThirdPageRoute(
secondId: 1,
filter: 'test',
).go(context)
ルーティング設定部分
まずは自動生成のファイル名を指定
part 'go_router_builder.g.dart';
GoRouterのグローバル変数を定義。$appRoutesは自動生成されたトップレベルのGoRoute配列のインスタンスです。
part 'go_router_builder.g.dart'; + final goRouterBuilder = GoRouter(routes: $appRoutes);
GoRouteDataを継承したThirdPageRouteクラスを作成。buildメソッドをオーバーライドし表示したいページを指定します。
go_routerの
- pathParametersの場合は
final int secondIdのようにnon nullにします - queryParametersの場合は
final String? fileterのようにoptionalにします
どちらもString型ではなくて良いです。
class ThirdPageRoute extends GoRouteData { ThirdPageRoute({required this.secondId, this.filter}); final int secondId; final String? filter; @override Widget build(BuildContext context, GoRouterState state) { return ThirdPage(secondId: secondId, filter: filter); } }
トップレベルのrouteには@TypedGoRouteアノテーションをつけます。
ルーティングの階層はgo_routerの時と同じようにroutes引数で行います。routes内ではTypedGoRoute<T>を指定し、Tには作成したSecondPageRouteクラスを指定します。
pathの指定はgo_routerと変わりません。
@TypedGoRoute<FirstPageRoute>( path: '/', routes: [ TypedGoRoute<SecondPageRoute>( path: 'second/:secondId', routes: [ TypedGoRoute<ThirdPageRoute>(path: 'third'), ], ), ], ) class FirstPageRoute extends GoRouteData { const FirstPageRoute(); @override Widget build(BuildContext context, GoRouterState state) { return const FirstPage(); } }
ここまで終わったら$ flutter pub run build_runner build をして、ファイルを自動生成させます。
遷移部分
画面遷移はGoRouteDataを継承したRouteクラスを使用します。
onPressed: () => ThirdPageRoute(
secondId: 1,
filter: 'test',
).go(context)
go_routerとgo_router_builderの比較
1. 自分で定数化する必要がない
go_router_builderではルーティング設定部分で定義したRouteクラスを使用するため、自分で定数化する必要がありません。
go_router
class ThirdPage extends StatelessWidget { static const routeName = 'third'; } onPressed: () => context.goNamed( ThirdPage.routeName, // ... )
go_router_builder
ThirdPageRoute(
secondId: 1,
filter: 'test',
).go(context)
2. pathParametersやqueryParametersの値の設定/取得の際にString型Keyを指定する必要がない
pathParamters、queryParamtersともにMap<String, String>型管理ではなくなるため、String型Keyを指定しなくてよいです。
go_router
context.goNamed( 'third', pathParameter: <String, String>{ 'secondId': '1' }, queryParameter: <String, String>{ 'filter': 'test' } ) GoRoute( name: 'third', path: 'third', builder: (context, state) => ThirdPage( secondId: int.parse(state.pathParameters['secondId']!), filter: state.queryParameters['filter'], ), ),
go_router_builder
ThirdPageRoute(
secondId: 1,
filter: 'test',
).go(context)
3. 型変換をする必要がない
go_routerの場合は自身で型変換をする必要がありましたが、go_router_builderを使用することによって、自動生成ファイル内でStringから指定した型に変換してくれるようになります。
go_router
GoRoute( name: 'third', path: 'third', builder: (context, state) => ThirdPage( secondId: int.parse(state.pathParameters['secondId']!), filter: state.queryParameters['filter'], ), ),
go_router_builder
class ThirdPageRoute extends GoRouteData { ThirdPageRoute({required this.secondId, this.filter}); final int secondId; final String? filter; @override Widget build(BuildContext context, GoRouterState state) { return ThirdPage(secondId: secondId, filter: filter); } }
go_router_builderのつらいところ
class ThirdPageRoute extends GoRouteData { ThirdPageRoute({required this.secondId, this.filter}); final int secondId; final String? hogeFuga; @override Widget build(BuildContext context, GoRouterState state) { return ThirdPage(secondId: secondId, filter: hogeFuga); } }
上記のように final String? hogeFugaとqueryParamtersを指定した場合、URLのクエリーパラメータのKeyが hoge-fuga とケバブケースになってしまいます。

issue化はされているのですが、未だ解決はされていないです。
https://github.com/flutter/flutter/issues/103023
まとめ
go_router_builderを使うことで、go_routerでString型で宣言されていた部分が自由に指定できるようになるのは便利だと感じました。
しかしクエリーパラメータのkeyが2単語以上の場合はケバブケースになってしますので、どうしても嫌な場合は1単語にするなど考慮が必要になってくると思います。