🤔

go_router学び直してみた

2023/02/10に公開

go_routeを学び直してみる

go_routerは、画面遷移のコードを短く書けるものだと思っていたのですが、違いました!

  • 使ってみて分かったこと?
    • Navigator.pushと違った所!
      • 戻るボタンがない?
      • ルートをネストさせると戻るボタンが現れる!
    • ボトムナビゲーションバーの設定が独特
      • GlobalKeyの設定が必要!
      • 画面遷移のコードは、context.goで全てできない!
      • 同じページから他のページへ移動するときに
      • GoRouter.of(context).go('/a/details');と書く必要があった!

ボトムナビゲーションバーを作ってみようとすると、独自の設定が必要みたいで結構ハマりました!
公式にサンプルコードがあったので、それを動かしながら、ページを追加したり画面遷移のルートを変更して、どうすれば画面遷移したときに、ルートが見つからないエラーに遭遇するのか、検証をしてみました。


GlobalKeyとは?

アプリ全体で一意となるキー。
グローバルキーは、要素を一意に識別する。グローバルキーは、それらの要素に関連する他のオブジェクトBuildContextなどへのアクセスを提供します。StatefulWidgetの場合、グローバルキーは、Stateへのアクセスも提供します。

ある記事によると?

名前の通り、任意の画面 (ページ) や Widget ツリーの全く別の階層から特定の Widget にアクセスするために利用します。基本的に親 Widget クラス内のメンバ変数などに定義し、StatefulWidgetの Widget に対して利用します。

利用シーン

2つの異なる画面で同じ状態のWidgetを表示したい場合
他のWidgetから特定のWidgetを参照したい場合


こちらのコードの<>の中のコードですね。日本語に翻訳してみると、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を最初に表示するようにしました。

router.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(

動作検証してみた

こちらが完成品のコード
英語のコメントを日本語に翻訳してます。
https://github.com/sakurakotubaki/Go-Router-BottomNavigationBar
公式のボトムナビゲーションバーのコード
https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/shell_route.dart
https://youtu.be/Px2bpBy60JI

今、個人開発で書いてるコードは設定の仕方が違いますが、基本的な設定は一緒なので大丈夫かなと思われます。

Discussion