Flutter の Navigator イケてないなと思って探していたら routemaster という良さげなライブラリ見つけたのでメモも兼ねて紹介します。
routemaster の特徴
routemaster は Flutter の Navigator 2.0 をラッピングしたライブラリで以下の特徴があります。
- URLとページのマッピングをシンプルに定義できる
- 使いやすくシンプルな API(例:
routemaster.push('/page')) - ネストされたタブにも、非常に簡単なやり方で対応できる
- 複数のルートマッピングを定義できる(ログイン済みとログアウト済みのユーザを分けたり)
- Observer を設定し、ルートの変化を簡単に聞き取ることができる
- 160個以上の Unit/Widget/Integrationテストで動作検証済み
インストール
flutter pub add routemaster
セットアップ
MaterialApp の routerDelegate に設定する
- contextありの設定
MaterialApp.router(
routerDelegate: RoutemasterDelegate(
routesBuilder: (context) => RouteMap(routes: {
'/': (routeData) => MaterialPage(child: PageOne()),
'/two': (routeData) => MaterialPage(child: PageTwo()),
}),
),
routeInformationParser: RoutemasterParser(),
)
- contextなしの設定
final routeMap = RouteMap(
routes: {
'/': (route) => MaterialPage(child: PageOne()),
'/two': (route) => MaterialPage(child: PageTwo()),
},
);
final routemaster = RoutemasterDelegate(
routesBuilder: (context) => routeMap,
);
MaterialApp.router(
routerDelegate: routemaster,
routeInformationParser: RoutemasterParser(),
)
遷移させる
あとは簡単に遷移させるだけ
/// 特定のパスに遷移する
Routemaster.of(context).push('/two')
/// 前のパスに戻る
Routemaster.of(context).pop()
Route にパラメータを持たせる
Path Parameter の場合
// Routemaster.of(context).push('products/123')で 123 がパラメータとして渡る
RouteMap(routes: {
'/products/:id': (route) => MaterialPage(
child: ProductPage(id: route.pathParameters['id']),
),
'/products/myPage': (route) => MaterialPage(child: MyPage()),
})
Query Parameter の場合
// Routemaster.of(context).push('/search?query=hello')でhelloがパラメータとして渡る
RouteMap(routes: {
'/search': (route) => MaterialPage(
child: SearchPage(query: route.queryParameters['query']),
),
})
現在のパス情報の取得
// フルパスの'/product/123?query=param'が取得できる
RouteData.of(context).path;
// Path ParameterのKeyとValueがMapで取得できる → Map: {'id': '123'}
RouteData.of(context).pathParameters;
// Query ParameterのKeyとValueがMapで取得できる → Map: {'query': 'param'}
RouteData.of(context).queryParameters;
Navigation の監視
RoutemasterObserver を継承したクラスを作り、RoutemasterDelegate に設定するだけ
class MyObserver extends RoutemasterObserver {
@override
void didPop(Route route, Route? previousRoute) {
print('ルートが戻ったよ');
}
@override
void didChangeRoute(RouteData routeData, Page page) {
print('新しいルートだよ: ${routeData.path}');
}
}
MaterialApp.router(
routerDelegate: RoutemasterDelegate(
observers: [MyObserver()],
routesBuilder: (_) => routeMap,
),
routeInformationParser: RoutemasterParser(),
);
Route ガード
条件によって Route の出し分けが可能です。
条件に一致しない場合、デフォルトの not found ページを表示する。
'/protected-route': (route) =>
canUserAccessPage()
? MaterialPage(child: ProtectedPage())
: NotFound()
条件に一致しない場合別のURLにリダイレクトさせる
'/protected-route': (route) =>
canUserAccessPage()
? MaterialPage(child: ProtectedPage())
: Redirect('/no-access'),
条件に一致しない場合別のURLにリダイレクトさせる(URLは変えない)
'/protected-route': (route) =>
canUserAccessPage()
? MaterialPage(child: ProtectedPage())
: MaterialPage(child: CustomNoAccessPage())
404 ページ
定義されていない URL の場合にエラーページに遷移させるようにする
RouteMap(
onUnknownRoute: (route, context) {
return MaterialPage(child: NotFoundPage());
},
routes: {
'/': (_) => MaterialPage(child: HomePage()),
},
)
リダイレクト
404 以外にもリダイレクトができます。
あるルートを別のルートにリダイレクトさせる
RouteMap(routes: {
'/one': (routeData) => MaterialPage(child: PageOne()),
'/two': (routeData) => Redirect('/one'),
})
定義されていない Route は全て Top に遷移させる
RouteMap(
onUnknownRoute: (_) => Redirect('/'),
routes: {
'/': (_) => MaterialPage(child: LoginPage()),
},
)
リダイレクト先にパラメータを引き渡す
RouteMap(routes: {
'/user/:id': (routeData) => MaterialPage(child: UserPage(id: id)),
'/profile/:uid': (routeData) => Redirect('/user/:uid'),
})
RouteMap の再構築
アプリ起動後の処理によって RouteMap 自体を置き換えることができます。 ログイン前とログイン後で Route が全く異なる場合など
final loggedOutMap = RouteMap(
onUnknownRoute: (route, context) => Redirect('/'),
routes: {
'/': (_) => MaterialPage(child: LoginPage()),
},
);
final loggedInMap = RouteMap(
routes: {
'/': (_) => MaterialPage(child: HomePage()),
},
);
MaterialApp.router(
routerDelegate: RoutemasterDelegate(
routesBuilder: (context) {
// AppStateの状態によってRouteMapを切り替える
final appState = Provider.of<AppState>(context);
return appState.isLoggedIn ? loggedInMap : loggedOutMap;
},
),
routeInformationParser: RoutemasterParser(),
);
最後に
README を記載しただけになってしまいましたが、いかがでしょうか。 必要な機能はすべて揃っているかなと個人的に思います。 全て実装して試したわけではないので、また必要があれば更新します。 ディープリンクに関しても、対応しているようなので別の記事でまとめてみようと思います。