go_router学び直してみた
go_routeを学び直してみる
go_routerは、画面遷移のコードを短く書けるものだと思っていたのですが、違いました!
- 使ってみて分かったこと?
- Navigator.pushと違った所!
- 戻るボタンがない?
- ルートをネストさせると戻るボタンが現れる!
- ボトムナビゲーションバーの設定が独特
- GlobalKeyの設定が必要!
- 画面遷移のコードは、context.goで全てできない!
- 同じページから他のページへ移動するときに
- GoRouter.of(context).go('/a/details');と書く必要があった!
- Navigator.pushと違った所!
ボトムナビゲーションバーを作ってみようとすると、独自の設定が必要みたいで結構ハマりました!
公式にサンプルコードがあったので、それを動かしながら、ページを追加したり画面遷移のルートを変更して、どうすれば画面遷移したときに、ルートが見つからないエラーに遭遇するのか、検証をしてみました。
GlobalKeyとは?
アプリ全体で一意となるキー。
グローバルキーは、要素を一意に識別する。グローバルキーは、それらの要素に関連する他のオブジェクトBuildContextなどへのアクセスを提供します。StatefulWidgetの場合、グローバルキーは、Stateへのアクセスも提供します。
ある記事によると?
名前の通り、任意の画面 (ページ) や Widget ツリーの全く別の階層から特定の Widget にアクセスするために利用します。基本的に親 Widget クラス内のメンバ変数などに定義し、StatefulWidgetの Widget に対して利用します。
利用シーン
2つの異なる画面で同じ状態のWidgetを表示したい場合
他のWidgetから特定のWidgetを参照したい場合
NavigatorStateとは?
こちらのコードの<>の中のコードですね。日本語に翻訳してみると、Navigator.ofを使用しないと使用できないようです?
final GlobalKey<NavigatorState> _rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root');
/// The state for a [Navigator] widget.
///
/// A reference to this class can be obtained by calling [Navigator.of].
翻訳すると
/// [Navigator]ウィジェットの状態です。
///
/// このクラスへの参照は、[Navigator.of] を呼び出すことで得ることができます。
今回の場合だと
恐らくgo_routerでは、ボトムナビゲーションバー内のページから画面遷移するには、GoRouter.of(context).go('/a/details');と書いて、パスがaの下のファイルに画面遷移してくれと、宣言しないとルートが見つからずに、エラーが出てしまうので、そのように書いてくれと言っているのだと思われます。
このように書かないと、ボトムナビゲーションバーのページAの詳細ページへは、画面遷移することはできませんでした!
ルートの設定
go_routerは設定したページしか画面遷移できないので、router.dartの中に、画面遷移するパスの設定をします。
今回だと、サンプルにあったボトムナビゲーションバーのあらかじめ設定されているルートと追加したファイルに、詳細ページへの画面遷移をするコードを書いたのと、通常のページから、ネストしたルートへ画面遷移する設定を書いて、戻るボタンを表示できるようにしてます。
ボトムナビゲーションバーのページが最初に表示されてましたが、ルートの設定を変更して、start_page.dartを最初に表示するようにしました。
import 'package:flutter/material.dart';
import 'package:go_route_navigationbar/ui/navigation/page/ScreenD.dart';
import 'package:go_route_navigationbar/ui/navigation/page/detail/DetailPage.dart';
import 'package:go_route_navigationbar/ui/navigation/page/detail/detail_screen.dart';
import 'package:go_route_navigationbar/ui/navigation/page/screenA.dart';
import 'package:go_route_navigationbar/ui/navigation/page/screenB.dart';
import 'package:go_route_navigationbar/ui/navigation/page/screenC.dart';
import 'package:go_route_navigationbar/ui/navigation/scafflod_with_navbar.dart';
import 'package:go_route_navigationbar/ui/page/start_datil.dart';
import 'package:go_route_navigationbar/ui/page/start_page.dart';
import 'package:go_router/go_router.dart';
// GlobalKeyを使用してボトムナビゲーションバーのWidgetにアクセスできるようにする.
final GlobalKey<NavigatorState> _rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _shellNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'shell');
final GoRouter goRouter = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/',
routes: <RouteBase>[
/// ボトムナビゲーションバーとは違うルート.
/// [ここから]
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return StartPage();
},
routes: <RouteBase>[
GoRoute(
path: 's',
builder: (BuildContext context, GoRouterState state) {
return const StartDetail();
},
),
]
),
/// [ここまで]
// -----------------------------------------------------------------
/// アプリケーションシェル
/// この中にボトムナビゲーションバーする設定を書く.
/// [ここから]
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (BuildContext context, GoRouterState state, Widget child) {
return ScaffoldWithNavBar(child: child);
},
routes: <RouteBase>[
/// 下部のナビゲーションバーに最初に表示される画面.
GoRoute(
path: '/a',
builder: (BuildContext context, GoRouterState state) {
return const ScreenA();
},
routes: <RouteBase>[
// 内側のナビゲータに重ねて表示する詳細画面。
// これは画面Aをカバーするが、アプリケーションシェルはカバーしない。
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen(label: 'A');
},
),
],
),
/// 下のナビゲーションバーで2番目の項目が選択されたときに表示されます。
/// 表示されます。
GoRoute(
path: '/b',
builder: (BuildContext context, GoRouterState state) {
return const ScreenB();
},
routes: <RouteBase>[
/// "/a/details "と同じですが、ルートNavigatorに表示させるために
/// parentNavigatorKey]を指定することで、ルートNavigatorに表示されます。これは画面Bとアプリケーションシェルの両方をカバーします。
/// アプリケーションシェルをカバーします。
GoRoute(
path: 'details',
parentNavigatorKey: _rootNavigatorKey,
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen(label: 'B');
},
),
],
),
/// 下部のナビゲーションバーに表示される3つ目の画面。
GoRoute(
path: '/c',
builder: (BuildContext context, GoRouterState state) {
return const ScreenC();
},
routes: <RouteBase>[
// 内側のナビゲータに重ねて表示する詳細画面。
// これは画面Aをカバーするが、アプリケーションシェルはカバーしない。
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen(label: 'C');
},
),
],
),
/// 下部のナビゲーションバーに表示される3つ目の画面。
GoRoute(
path: '/d',
builder: (BuildContext context, GoRouterState state) {
return const PageD();
},
routes: <RouteBase>[
// 内側のナビゲータに重ねて表示する詳細画面。
// これは画面Aをカバーするが、アプリケーションシェルはカバーしない。
GoRoute(
path: 'dd',
builder: (BuildContext context, GoRouterState state) {
return const DetailPage();
},
),
],
),
],
),
/// [ここまで]
],
);
公式のサンプルだと、ScreenA()〜ScreenC()までは、詳細ページは、実は全て同じで、値を画面遷移先の詳細ページに渡しているので、違う画面に見えるだけです。
PageD()と、DetailPage()は私が追加しました。
試してみて分かったこと
ボトムナビゲーションバーに表示されている4つのページの詳細ページか何か追加したページに画面遷移するには、以下のコードを書かないとルートのエラーが発生しました!
// BottomNavigationBar内のページで、
// 詳細ページへ画面遷移するときは、以下のコードを書く.
GoRouter.of(context).go('/a/details');
popは使えない?
以前試したときは、動いていたはずなんですけど新しいバージョンはできないのか、ネストしていないルートへ戻るボタンが欲しかったので、以下のコードを書いて追加しました。
パスを[/]にして、最初のページへ戻るように設定しました。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// ネストしていないルートだと戻るボタンが現れない!
leading: IconButton(
onPressed: () {
context.go('/');
},
icon: Icon(Icons.arrow_back_ios),
),
),
body: Center(
動作検証してみた
こちらが完成品のコード
英語のコメントを日本語に翻訳してます。
公式のボトムナビゲーションバーのコード
今、個人開発で書いてるコードは設定の仕方が違いますが、基本的な設定は一緒なので大丈夫かなと思われます。
Discussion