FlutterでBottomNavigationBarを使う場合、
currentIndex
などを使う必要があるけど、go_routerでパスと一致させたいなと思って、
いろいろ試したときの備忘録(*´ω`*)
StatefulShellRoute.indexedStack
を使うといいらしい。
が、ドキュメントにはほぼ説明がないっぽい。。
サンプルコード
GoRouter
部分はこんな感じ。
ShellRouteと同じような感じで、
固定したい部分(Scaffold
,AppBar
,BottomNavigationBar
など)と
遷移したい部分を分ける感じ。
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_samples/layout/app_layout.dart'; import 'package:flutter_samples/pages/label_page.dart'; import 'package:go_router/go_router.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part "app_router.g.dart"; // rootのState付きNavigator final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root'); // "/tab/a" 配下のState付きNavigator final GlobalKey<NavigatorState> _shellTabANavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'shellTabA'); // "/tab/b" 配下のState付きNavigator final GlobalKey<NavigatorState> _shellTabBNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'shellTabB'); @riverpod class AppRouter extends _$AppRouter { @override GoRouter build() { return GoRouter( navigatorKey: _rootNavigatorKey, debugLogDiagnostics: kDebugMode, initialLocation: "/tab/a", routes: <RouteBase>[ // index付きのStatefulShellRouteを使う StatefulShellRoute.indexedStack( // Scaffoldなど描画を固定したい部分のWidgetを返す builder: (context, state, navigationShell) { return AppLayout(navigationShell: navigationShell); }, branches: [ StatefulShellBranch( navigatorKey: _shellTabANavigatorKey, routes: [ GoRoute(path: "/tab/a", builder: _labelPegeBuilder("TAB A")), ], ), StatefulShellBranch( navigatorKey: _shellTabBNavigatorKey, routes: [ GoRoute(path: "/tab/b", builder: _labelPegeBuilder("TAB B")) ], ), ], ), ], ); } // サンプルページを返すだけのBuilder GoRouterWidgetBuilder _labelPegeBuilder(String label) { return (context, state) => Center(child: Text(label)); } }
StatefulNavigationShell(navigationShell
)が受け取れるようになり、
そこにpathと一致したGoRouteのWidget(navigationShell
自体)と、
branches
内のindex(navigationShell.currentIndex
)を取得できる。
これらを使って、BottomNavigationBarに設定することで、
パスとタブを一致させることができるよう。
なので、Scaffold
とBottomNavigationBar
を含むAppLayout
はこんな感じ。
class AppLayout extends HookConsumerWidget { // navigationShellを受け取るように追加 const AppLayout({super.key, required this.navigationShell}); final StatefulNavigationShell navigationShell; @override Widget build(BuildContext context, WidgetRef ref) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("Flutter Samples"), ), // StatefulNavigationShellのWidgetを利用 body: navigationShell, bottomNavigationBar: BottomNavigationBar( // StatefulNavigationShellのindexを利用 currentIndex: navigationShell.currentIndex, // goBranch()を使って、タブを切り替え onTap: (index) => navigationShell.goBranch(index), items: const [ BottomNavigationBarItem( icon: Icon(Icons.home_outlined), label: "TAB A", ), BottomNavigationBarItem( icon: Icon(Icons.article_outlined), label: "TAB B", ) ], ), ); } }
ShellRoute
ドキュメントより翻訳&抜粋。
一致する子ルートの周囲にUI shellを表示するルート
ShellRouteがGoRouterかGoRouteのルートのリストに追加されると、 rootのNavigatorではなく、新しいNavigatorを使って、サブルートを表示する
別のNavigatorで子ルートを表示するには、GoRouterまたはShellRouteに提供されたキーと一致するparentNavigatorKey をそのナビゲーターに設定する。
- ShellRouteでは、rootのNavigatorとは別に、
新しいNavigatorを利用する - 同じNavigator上に表示したい場合は、
parentNavigatorKey
を設定する
らしい
StatefulShellRoute
ドキュメントより翻訳&抜粋。
サブルート用に個別のNavigatorを備えた UI shellを表示するルート。
ShellRouteと似ているが、子ルートごとにNavigatorを持つ点で異なる。
BottomNavigationBarなどを使う場合に便利ShellRouteと同様に、
builder
などが必要だが、
StatefulNavigationShellを受け取る点で異なる。StatefulShellRouteは一連のnavigation stacksを維持するため、
ブランチ間の切り替え時の遷移はbranch Navigator container(つまり、navigatorContainerBuilder) の責務である。デフォルトの
IndexedStack
の実装であるStatefulShellRoute.indexedStack
には、 アニメーションは含まれない。しかし、これを実現する例を提供する。
- 子ルート(StatefulShellBranch)ごとに、新しいNavigatorを利用
- StatefulShellBranch間のアニメーションは、自身で実装する必要がある。
サンプルはこのあたり。
- custom_stateful_shell_route.dart at main · flutter/packages
- stateful_shell_route.dart at main · flutter/packages
NavigatorState
NavigatorのStateクラス
ソースの中身はこんな感じ。_history
などを持っている。
class NavigatorState extends State<Navigator> with TickerProviderStateMixin, RestorationMixin { late GlobalKey<OverlayState> _overlayKey; List<_RouteEntry> _history = <_RouteEntry>[]; // ... bool canPop() { final Iterator<_RouteEntry> iterator = _history.where(_RouteEntry.isPresentPredicate).iterator; if (!iterator.moveNext()) { // We have no active routes, so we can't pop. return false; } }
class Navigator extends StatefulWidget { // Navigator.of(context)は、NavigatorStateを返す static NavigatorState of(BuildContext context, { bool rootNavigator = false }) { // ... } // canPopはNavigatorStateのcanPopを呼び出していて static bool canPop(BuildContext context) { final NavigatorState? navigator = Navigator.maybeOf(context); return navigator != null && navigator.canPop(); } } // NavigatorState.canPopは_historyを見ている class NavigatorState extends State<Navigator> with TickerProviderStateMixin, RestorationMixin { List<_RouteEntry> _history = <_RouteEntry>[]; // ... bool canPop() { final Iterator<_RouteEntry> iterator = _history.where(_RouteEntry.isPresentPredicate).iterator; if (!iterator.moveNext()) { // We have no active routes, so we can't pop. return false; } // ... } }
なので、Navigator
が異なるというのは、スタック自体も別になっているよう。
parentNavigatorKey
を設定する=同じNavigatorのスタックを利用する
って感じなのかな(*´ω`*)?
以上!! GoRouterとかNavigatorとか、なんとなくわかってきた気がする...(*´ω`*)
参考にしたサイト様
- Flutter Bottom Navigation Bar with Stateful Nested Routes using GoRouter
- flutter - Is it possible to implement Navigation bar using GoRouter Package? - Stack Overflow
- flutter - Is it possible to implement Navigation bar using GoRouter Package? - Stack Overflow
- StatefulShellRoute.indexedStack constructor - StatefulShellRoute - go_router library - Dart API
- [go_router] Nested stateful navigation with ShellRoute by tolo · Pull Request #2650 · flutter/packages
- [Flutter] go_router と hooks_riverpod と BottomNavigationBar を使って画面遷移を実装する
- 【Flutter】go_routerでBottomNavigationBarの永続化に挑戦する
- packages/packages/go_router/example/lib/stateful_shell_route.dart at main · flutter/packages