【Flutter】ボトムナビゲーションバーを表示したまま画面遷移したい【auto_route】
はじめに
ずっと go_router と go_router_builder を使ってましたが、ボトムナビゲーションバーを表示したまま画面遷移するように ShellRoute
を使って修正をしたら、タブを行き来すると画面スタックがクリアされてしまう問題 が go_router では解決できませんでした。そのあたりをサポートしている auto_route に最近乗り換えたので実装方法を紹介します!
ちなみに、みんな大好きアンドレアさんは Beamer をオススメしてました(試してない)。
こんなことができます
auto_route とは?
タイプセーフな引数の受け渡しとディープリンクを可能にし、コード生成を使用してボイラープレートを最小限にしてくれる Flutter ナビゲーション向けのパッケージです。
主に次の特徴があります。ナビゲーションパッケージに必要な基本的な機能はすべて備わっていると思います。
- ディープリンク対応
- コード生成を使用してボイラープレートを最小限にする
-
push()
やpop()
から戻り値を返すことができる - ボトムナビゲーションバー、タブバーなどネストされたナビゲーションに対応
-
BuildContext
なしでナビゲートができる - ユーザー認証状態に応じたリダイレクト(ルートガード)に対応
- トランジション(画面遷移時のアニメーション)のカスタマイズ
環境
Flutter 3.7.12 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 4d9e56e694 (9 days ago) • 2023-04-17 21:47:46 -0400
Engine • revision 1a65d409c7
Tools • Dart 2.19.6 • DevTools 2.20.1
auto_route の導入方法
基本的には 公式のセットアップと使用法 通りにセットアップしていけばよいんですが、ところどころ誤記などもあるので、ゆーと さんが書いてくれた次の手順も参考にするとよきです!
ボトムナビゲーションバーがあるシンプルな画面を実装する
まず、次のように AutoTabsRouter
を使ってボトムナビゲーションバーがあるシンプルな画面を実装してみます。
各タブに表示する画面を用意する
各タブに表示する画面 Widget に @RoutePage()
アノテーションを追加しておきます。これでホーム画面へ遷移できるようになります。他の各タブ内の画面も同様に追加しておきます。
+()
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ホーム'),
),
);
}
}
土台となるルートページを用意する
次に、ボトムナビゲーションバーをもつ土台となるルートページを AutoTabsRouter
を使い次のように実装します。
タブ押下で tabsRouter.setActiveIndex(index)
が実行されてタブ押下イベントが AutoTabsRouter
に伝わります。AutoTabsRouter
がタブを切り替えると builder(context, child)
が発火してタブ内の画面が切り替わります。
()
class RootPage extends StatelessWidget {
const RootPage({super.key});
Widget build(BuildContext context) {
return AutoTabsRouter(
routes: const [
// ここに各タブ画面のルートを追加する
HomeRoute(),
MypageRoute(),
],
builder: (context, child) {
// タブが切り替わると発火します
final tabsRouter = context.tabsRouter;
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
selectedIndex: tabsRouter.activeIndex,
destinations: const [
NavigationDestination(
icon: Icon(Icons.home),
label: 'ホーム',
),
NavigationDestination(
icon: Icon(Icons.account_circle),
label: 'マイページ',
),
],
onDestinationSelected: tabsRouter.setActiveIndex,
),
);
},
);
}
}
ルート定義を実装する
最後にルート定義を次のように実装します。初期表示時は /
にルーティングされてきます。/
にルーティングされたら RootPage
を表示します。RootPage
は、さらに 2 つのタブ画面を表示する、という流れです。
part 'app_router.gr.dart';
@AutoRouterConfig(replaceInRouteName: 'Page,Route')
class AppRouter extends _$AppRouter {
List<AutoRoute> get routes => [
+ AutoRoute(
+ path: '/',
+ page: RootRoute.page,
+ children: [
+ AutoRoute(
+ path: 'home',
+ page: HomeRoute.page,
+ ),
+ AutoRoute(
+ path: 'mypage',
+ page: MypageRoute.page,
+ ),
+ ],
+ ),
];
}
これでひとまずボトムナビゲーションバーがある画面が実装できました!
タブ内で画面遷移できるようにする
ホーム画面からお気に入り画面に遷移できるようにして、タブ内で画面遷移をしてみます。
最初にダメなパターンをやってみて、うまくいくように直してみます。
ダメなパターン
実際に私が陥ったダメなパターンで実装してみます。まず、お気に入り画面を新たに用意します。
()
class FavoritePage extends StatelessWidget {
const FavoritePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('お気に入り'),
),
);
}
}
ホーム画面から遷移するので、ホーム画面のルート配下にお気に入り画面のルートを追加します。
List<AutoRoute> get routes => [
AutoRoute(
path: '/',
page: RootRoute.page,
children: [
AutoRoute(
path: 'home',
page: HomeRoute.page,
+ children: [
+ AutoRoute(
+ path: 'favorite',
+ page: FavoriteRoute.page,
+ ),
+ ],
),
・・・
ホーム画面にお気に入り画面へ遷移するためのボタンを追加します。これでよさそう!
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ホーム'),
),
+ body: ElevatedButton(
+ onPressed: () => context.navigateTo(const FavoriteRoute()),
+ child: const Text('お気に入り'),
+ ),
);
}
しかし、動かしてみると次のようにうまくいきません。なぜうまくいかないのでしょうか?
ルーターは孫ルートは検索しない
navigateTo()
による画面遷移をするとき、ルート定義内の最も近い親ルーターをみつけ、そのルーターが遷移先のルートを検索します。ルーターが遷移先のルートを検索するときは、孫ルートは検索せず、子ルートまでしか検索してくれません。
ルーターを見つけるロジックについては次で説明されています(ルーターがルートを見つけるロジックについては特に書かれていませんでした)。
今回の実装のルート定義を図示してみます。ホーム画面からみつかる親ルーターは TabsRouter
です。TabsRouter
からお気に入り画面は見つかりません。
うまくいくパターン
ホーム画面とお気に入り画面を同じルーターの下にぶら下げるとうまくいきそうです。TabsRouter
とホーム画面の間に新しいルーターを追加して、ホーム画面とお気に入り画面を兄弟の関係にしてみます。
新しいルーター HomeRouterRoute
を追加します。
+()
+class HomeRouterPage extends AutoRouter {
+ const HomeRouterPage({super.key});
+}
次のように、RootRoute
と HomeRoute
の間に HomeRouterRoute
を挟みます。HomeRoute
は initial: true
とすることで、/home
は HomeRoute
にマッピングされます。これでホーム画面とお気に入り画面は同じルーターの下にぶら下がる関係になりました。
List<AutoRoute> get routes => [
AutoRoute(
path: '/',
page: RootRoute.page,
children: [
AutoRoute(
path: 'home',
- page: HomeRoute.page,
- children: [
+ page: HomeRouterRoute.page,
+ children: [
+ AutoRoute(
+ initial: true,
+ page: HomeRoute.page,
+ ),
AutoRoute(
path: 'favorite',
page: FavoriteRoute.page,
),
],
),
AutoRoute(
path: 'mypage',
page: MypageRoute.page,
),
],
),
];
忘れずに AutoTabsRouter
の routes
も変更しておきます。
@RoutePage()
class RootPage extends StatelessWidget {
const RootPage({super.key});
Widget build(BuildContext context) {
return AutoTabsRouter(
routes: const [
- HomeRoute(),
+ HomeRouterRoute(),
MypageRoute(),
],
うまく動きました!タブを行き来しても画面スタックは維持されています 🎉
サンプルコードを公開しています
本記事で紹介したサンプルコードを公開しています!
もっと auto_route を活用したい
本記事で紹介したシンプルな実装より、より実践に近いサンプルコードも公開しています!是非参考にしてみてください!
- Riverpod 対応
- ユーザーのサインイン状態に応じた初期表示画面の出し分け(ガード対応)
-
AutoRouterObserver
を使ったログ出力 - トランジションのカスタマイズ
- 別のルーター配下の画面への遷移
- 画面遷移時に画面スタックを自動で積む方法
- 良い感じのディレクトリ構成
最後に
Flutter 大学という Flutter エンジニアに特化した学習コミュニティに所属しています。オンラインでわいわい議論したり、Flutter の最新情報をゲットしたりできます!ぜひ Flutter 界隈を盛り上げていきましょう!
Discussion