タクシーアプリ『GO』の iOS アプリを開発している井戸田です。
今回はFlutterのgo_routerライブラリをもっと便利に使うことができるgo_router_builderというライブラリについて紹介します。
go_routerはFlutter公式で出している、Navigator2.0を簡単に扱えることができるライブラリです。 URLベースで遷移したい画面を指定することができます。
https://pub.dev/packages/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のつらいところの1つとして全てStringで制御しないといけないところだと思います。
name(third)の部分はStringのままでもいいですが定数化したくなると思います。
Before
context.goNamed(
'third',
// ...
)
After
class ThirdPage extend StatelessWidget {
static const routeName = 'third';
}
context.goNamed(
ThirdPage.routeName,
// ...
)
'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'
}
)
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の拡張ライブラリで、Flutter公式のライブラリです。
go_routerの機能更新に追従してgo_router_builderでもできるようになっているため、基本的にgo_routerでできることはgo_router_builderでもできます。
ファイルを自動生成するためbuild_runnerが必要になります。
https://pub.dev/packages/go_router_builder
ルーティング設定部分
part 'go_router_builder.g.dart';
final goRouterBuilder = GoRouter(routes: $appRoutes);
<FirstPageRoute>(
path: '/',
routes: [
TypedGoRoute<SecondPageRoute>(
path: 'second/:secondId',
routes: [
TypedGoRoute<ThirdPageRoute>(path: 'third'),
],
),
],
)
class FirstPageRoute extends GoRouteData {
const FirstPageRoute();
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;
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の
どちらもString型ではなくて良いです。
class ThirdPageRoute extends GoRouteData {
ThirdPageRoute({required this.secondId, this.filter});
final int secondId;
final String? filter;
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と変わりません。
<FirstPageRoute>(
path: '/',
routes: [
TypedGoRoute<SecondPageRoute>(
path: 'second/:secondId',
routes: [
TypedGoRoute<ThirdPageRoute>(path: 'third'),
],
),
],
)
class FirstPageRoute extends GoRouteData {
const FirstPageRoute();
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_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)
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)
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;
Widget build(BuildContext context, GoRouterState state) {
return ThirdPage(secondId: secondId, filter: filter);
}
}
class ThirdPageRoute extends GoRouteData {
ThirdPageRoute({required this.secondId, this.filter});
final int secondId;
final String? hogeFuga;
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単語にするなど考慮が必要になってくると思います。
興味のある方は 採用ページ も見ていただけると嬉しいです。
Twitter @goinc_techtalk のフォローもよろしくお願いします!