GoRouterやAutoRoute等の付き合い方
結論
- DeepLinkに対応するなら、現状
GoRouter
一択。やらないなら、どっちでもいい。 - Flutter Web非対応、DeepLink非対応なら、これらのパッケージを採用しなくても良い。
- Route単位でGuardできるAutoRouteの仕組みがGoRouterにあれば最AND高
そもそもGoRouterやAutoRouteの採用理由はどこか
Flutter公式もあまり言わなくなりましたけど、Navigator 2.0
っていうお気持ち文章を公式が発信しておりました。
簡単に言うと、スタックで1個1個履歴を積むやり方をすると、category/men/item/143413869
みたいなURLをクリックされたらパスの階層に呼応する画面階層を再現できないので、「画面階層まるごと」差し替えられるようにしないとWebの世界では非常に厳しいという話です。
ネイティブアプリには、URLを入れるアドレスバーなんてものはありませんので、スタックで1個1個積んで取れば良い。Webはユーザーが任意の画面階層を持つページにジャンプできちゃうので、対応しないといけない。
なので、スタックで出し入れするのは無理ゲーってことで、画面遷移のスタック全体を再構築できるようにしないといけなくなり、Router API
ってのが作られた。
Nagivator.push
などを使うAPIを Navigator1.0
、router.go
のような Router API
を使うのを Navigatior 2.0
と言っていたように思います。
なので、そもそもFlutter Webをやらないプロジェクトなら、Navigator2.0の利用シーンが極めて小さいように思います。ルーティングライブラリを使わんでも生きていけますよ。宣言的になった所で大したメリット無いと思う。
DeepLinkやるならGoRouterしかない
AutoRouteの最新版7.8.x
系にはDeepLinkに関して致命的なバグがあります。アプリがフォアグラウンドもしくはバックグラウンドで待機している時にリンクをタップしても、DeepLinkを処理するイベントハンドラーが作動しないというものです。ゼロからプロジェクト作って、AutoRouteだけインストールした状態でも、動かず。
CAさんのWinTicketでAutoRouteでDeepLink捌いているっていう資料がありましたが、AutoRouteのバージョンが5系で固めているとあったので、そういうことかもしれません。
DeepLinkがマストなプロジェクトなら、GoRouterしか選択肢がなくなります。GoRouterは、フォアグラウンド・バックグラウンド、どっちでも動きます。
GoRouterでDeepLinkをさばく方法
単純です。Route作ってパスを指定するだけです。
routes: [
TypedGoRoute<HomeRoute>(
path: '/',
routes: [
TypedGoRoute<CatalogRoute>(
path: 'catalog',
),
TypedGoRoute<CategoryRoute>(
path: 'category/:categoryId',
routes: [
TypedGoRoute<ItemRoute>(
path: 'item/:itemId',
),
],
),
TypedGoRoute<CampaignRoute>(
path: 'campaign',
),
],
),
],
/campaign
に来れば、CampaignRouteが自動的にマッチして呼び出されます。
/category/men/item/222
の場合は、:categoryId
に men
、:itemId
に 222
が入ります。
この時、HomeRoute
-> CategoryRoute
-> ItemRoute
の画面階層が再現されます。表示されるのはItemRouteのページだけど、戻るでCategoryRoute、そこから戻ってHomeRouteに移動できます。
context.go("/category/men/item/222")
って書くと、上記のような動きをします。go
メソッドはスタックそのものを入れ替えます。push
との違いはそこです。
GoRouterでRouteをネストする
routes
をネストすることで親子関係が作れますが、子は親のRouteで定義したパラメーターを全部引き継がないといけません。 ItemRouteは、categoryId
を受け取るようにしないとダメ。
この仕様がマッチしないのは、アプリ内で遷移する時に、ページとなるWidgetにオブジェクトを渡している場合です。文字列や整数じゃなくuserオブジェクト、みたいなケース。
GoRouterは$extra
でオブジェクトを渡せますが、親の$extra
を子のRouteが受け取れないようです。つまり、親子関係を定義した場合$extra
は実質使えなくなります。URLにオブジェクト突っ込めねえし。
個人的にはGoRouterで定義したroutes
は、DeepLink Handlerとして受け取るものを書いて、$extra
を使う場合はその配下にサブルートを持たないようにしています。
多分ですけど、$extra
は苦肉の策で使うもので、GoRouterにおいては非推奨なんじゃないかなって思う。
回遊性のあるナビゲーション対応は無理
ECでよくある、レコメンドの対応ですね。Amazonで商品を見たらおすすめがでます。あれをタップすると、WebであればURLが代わってページが変わり、戻るボタンで前のページに戻ります。
アプリでやると、商品→レコメンドの商品→またレコメンドの商品って感じでPUSHして、スタックを積みます。これをGoRouterでどうやるのか悩んでいます。URLのパスが再帰構造になってしまう(スタックの最終地点が事前にわからない)ので、事前にルートを定義するGoRouterと相性が悪すぎます。
Route Guard ほしい時がある、が。
この画面に遷移するときはこういう条件を満たさないとダメっていうのが、AutoRouteはできる。
GoRouterでもやれなくはない。以下のようにredirect
ハンドラーを定義すれば、現在のパスが変わったらイベントが走る。でも、ifの嵐になりそう。
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'app_routes.dart';
part 'go_router.g.dart';
(keepAlive: true)
GoRouter router(RouterRef ref) => GoRouter(
routes: $appRoutes,
initialLocation: '/splash',
onException: (context, state, router) {
return;
},
redirect: (context, state) async {
return null;
},
);
GoRouterは開発が盛ん(という名のバグつぶしが多い)印象で、APIもまぁまぁ変わる。バグもまぁまぁ多い、これは仕方ないのかなと思う。ルーティング周りってFlutterの沼っぽいし、Navigator2.0自体が相当面倒なことを頑張ってやっているようなので。
URLはホストの持っているデータ構造を表現するためにコンテキストパス等の考えができたのがそもそもの発想なので、データの階層構造≠画面の階層構造な所が、なかなかどうして。
というわけで、Flutterのルーティングについてのメモでした。おしまい。
Discussion