🔃

go_routerの魅力を語る

2023/06/28に公開3

概要

Flutterを始めて少し時間が経った際に、go_routerの導入を検討するタイミングがあるかと思います。
go_routerを導入して少し経って魅力が分かって来たので、備忘録的にこの記事に書き記しておきます。
リポジトリは下記になります。
https://github.com/sukekyo000/try_go_router/tree/zenn

前提条件

・Flutter(dart)の基本的な書き方が分かる
・go_routerの基本解説はありません

バージョン

・Flutter 3.10.5
・go_router 9.0.0

go_routerの魅力

魅力は下記の5点かと思います。
・アプリ全体のルーティングを1つのファイルにまとめることができる
・画面遷移のアニメーションをまとめることができる
・遷移時の処理がすっきりする
・ボトムナビゲーションバーの出し入れを簡単にできる
・条件に応じてリダイレクトをかけれる
1つずつ解説します。

アプリ全体のルーティングを1つのファイルにまとめることができる

例えば下記のようなアプリを想定します。

・矢印の遷移は各ページにあるボタンからの遷移
・HomeBottomBarにHomePageとHomeDetailPageがある
・MyPageBottomBarにMyPageとSettingsPageがある
・NonBottomPageにボトムナビゲーションバーはない
・各ボトムナビゲーションバーの状態を保持する
上記のアプリを想定した場合、ルーティングファイルは以下になります。

final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _homeNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'home');
final GlobalKey<NavigatorState> _myPageNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'my_page');

final GoRouter router = GoRouter(
  navigatorKey: _rootNavigatorKey,
  initialLocation: '/home',
  routes: <RouteBase> [
    /// ボトムナビゲーションバー有り
    StatefulShellRoute.indexedStack(
      builder: (context, state, navigationShell){
        return ScaffoldWithNavBar(navigationShell: navigationShell,);
      },
      branches: <StatefulShellBranch>[
        StatefulShellBranch(
          navigatorKey: _homeNavigatorKey,
          routes: <RouteBase>[
            GoRoute(
              path: '/home',
              pageBuilder: (context, state) => const NoTransitionPage(child: HomePage()),
              routes: [
                GoRoute(
                  path: 'detail',
                  builder: (context, state) => const HomeDetailPage(),
                ),
              ],
            ),
          ],
        ),
        StatefulShellBranch(
          navigatorKey: _myPageNavigatorKey,
          routes: [
            GoRoute(
              path: '/my_page',
              pageBuilder: (context, state) => const NoTransitionPage(child: MyPagePage()),
              routes: [
                GoRoute(
                  path: 'settings',
                  builder: (context, state) => const SettingsPage(),
                ),
              ],
            ),
          ],
        ),
      ],
    ),
    /// ボトムナビゲーションバー無し
    GoRoute(
      parentNavigatorKey: _rootNavigatorKey,
      path: '/non_nav',
      pageBuilder: (context, state) => const MaterialPage(
        fullscreenDialog: true,
        child: NonBottomNavBarPage()
      ),
    )
  ]
);

より複雑なアプリになった場合、記述量が増えますが1つにファイルにまとまっているのは個人的には可読性が上がると思っております。

画面遷移のアニメーションをまとめることができる

例えばボトムナビゲーションバー無しのページに、下から上へのアニメーションを実装すると、

class BottomNavBarAnimation {
  // 下から上へのアニメーション
  CustomTransitionPage transitionWithBottomToUp(Widget page){
    return CustomTransitionPage(
      child: page,
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        // 遷移時のアニメーションを指定
        const Offset begin = Offset(0.0, 1.0);
        const Offset end = Offset.zero;
        final Tween<Offset> tween = Tween(begin: begin, end: end);
        final Animation<Offset> offsetAnimation = animation.drive(tween);
        return SlideTransition(
          position: offsetAnimation,
          child: child,
        );
      },
    );
  }
}
BottomNavBarAnimation bottomNavBarAnimation = BottomNavBarAnimation();
...
/// ボトムナビゲーションバー無し
GoRoute(
  parentNavigatorKey: _rootNavigatorKey,
  path: '/non_nav',
  pageBuilder: (context, state) => bottomNavBarAnimation.transitionWithBottomToUp(const NonBottomNavBarPage()),
)

アニメーションは多用すると思うので、1ヵ所にまとめておけます。

遷移時の処理がすっきりする

下記をご覧になると一発で分かると思います。
ここもボトムナビゲーションバー無しのページを例に挙げます。

onPressed: () {
// go_router使用時
context.push("/non_nav");

// go_router未使用時
// Navigator.of(context).push(
//   PageRouteBuilder(
//     pageBuilder: (context, animation, secondaryAnimation) => const NonBottomNavBarPage(),
//     transitionsBuilder: (context, animation, secondaryAnimation, child) {
//       // 遷移時のアニメーションを指定
//       const Offset begin = Offset(0.0, 1.0);
//       const Offset end = Offset.zero;
//       final Tween<Offset> tween = Tween(begin: begin, end: end);
//       final Animation<Offset> offsetAnimation = animation.drive(tween);
//       return SlideTransition(
//         position: offsetAnimation,
//         child: child,
//       );
//     },
//   )
// );
},

ボトムナビゲーションバーの出し入れを簡単にできる

ボトムバーを出し入れを制御しているのは、

final GlobalKey<NavigatorState> _rootNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _homeNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'home');
final GlobalKey<NavigatorState> _myPageNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'my_page');

上記の部分になります。
GoRouterStatefulShellBranchに上記のキーを適切に入れることで、ボトムナビゲーションバーの出し入れが簡単になります。
また、例に出しているアプリでいうとHomeBottomBarのHomeDetailPageからMyPageBottomBarのSettingsPageにボトムバーの状態を変更しつつ遷移できることもメリットだと感じています。

条件に応じてリダイレクトをかけれる

最後にリダイレクトです。
例えば、マイページタブのSettingsPageに遷移するとき、ログイン状態でないときはログインページに飛ばす場合は下記のような処理をかけるとリダイレクトしてくれます。

StatefulShellBranch(
  navigatorKey: _myPageNavigatorKey,
  routes: [
    GoRoute(
      path: '/my_page',
      pageBuilder: (context, state) => const NoTransitionPage(child: MyPagePage()),
      routes: [
	GoRoute(
	  path: 'settings',
	  redirect: (BuildContext context, GoRouterState state) {
	    if(login == false){
	      return '/my_page/login';
	    }
	    return null;
	  },
	  builder: (context, state) => const SettingsPage(),
	),
      ],
    ),
  ],
),

riverpodなどの状態管理を加えるとよりリダイレクトが容易になりそうですね。

まとめ

個人的には当分の間はgo_routerで画面遷移を管理していこうと思っております。
質問等あればコメントいただけると嬉しいです!

Discussion

JboyHashimotoJboyHashimoto

StatefulShellBranchの中に、リダイレクトの処理を書くのが今は流行っているのですか?
私の書き方が古いかもと思ってご質問してみました。

sukekyosukekyo

流行っているかどうかは存じ上げないです🙇‍♂️
こちらの記事では「マイページタブのSettingsPage」にリダイレクト処理を実行すると設定したため、StatefulShellBranchにリダイレクト処理を書きました。
個人的にはGoRouterやGoRouteにリダイレクト処理を書いても問題ないと思います。

JboyHashimotoJboyHashimoto

川崎さんご回答ありがとうございます!
私も記事にしてくれた内容を試してみたいと思います。