🛹

AutoRoute BottomNavigationBar

2024/04/04に公開1

📕Overview

タブナビゲーション
もしあなたがflutter mobileを使っているのであれば、タブナビゲーションを実装する可能性が高いでしょう。
前の例では、入れ子になった子ルートをレンダリングするためにAutoRouterウィジェットを使いましたが、AutoRouterは AutoStackRouterの単なるショートカットです。AutoRouterはAutoStackRouterのショートカットにすぎません。StackRouterは、その内部でページのスタックを管理し、アクティブ/可視ページが常に一番上にあり、その下のページを見るにはそれをポップする必要があります。
タブが変わるたびにネストされたルートをプッシュしたり置き換えたりすることで、AutoRouter(StackRouter) を使ってタブを実装することもできます。
AutoTabsRouterを使えば、オフステージルートの状態を保持したまま異なるルートを切り替えることができ、タブルートはデフォルトで遅延ロードされ(無効にすることも可能)、最終的には好きなトランジションアニメーションを作成することができます。
先ほどの例をタブナビゲーションを使うように変更してみましょう。
ルート宣言マップは何も変更せず、users、posts、settingsの3つのネストした子を持つダッシュボードページがあることに注目してください。

今回は、公式のコードを参考にAutoRouteでBottomNavigationBarを実装してみましょう!

class DashboardPage extends StatelessWidget {

  
  Widget build(BuildContext context) {
    return AutoTabsRouter(
      // list of your tab routes
      // routes used here must be declared as children
      // routes of /dashboard
      routes: const [
        UsersRoute(),
        PostsRoute(),
        SettingsRoute(),
      ],
      transitionBuilder: (context,child,animation) => FadeTransition(
            opacity: animation,
            // the passed child is technically our animated selected-tab page
            child: child,
          ),
      builder: (context, child) {
        // obtain the scoped TabsRouter controller using context
        final tabsRouter = AutoTabsRouter.of(context);
        // Here we're building our Scaffold inside of AutoTabsRouter
        // to access the tabsRouter controller provided in this context
        //
        // alternatively, you could use a global key
        return Scaffold(
          body: child,
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: tabsRouter.activeIndex,
            onTap: (index) {
              // here we switch between tabs
              tabsRouter.setActiveIndex(index);
            },
            items: [
              BottomNavigationBarItem(label: 'Users', ...),
              BottomNavigationBarItem(label: 'Posts', ...),
              BottomNavigationBarItem(label: 'Settings', ...),
            ],
          ),
        );
      },
    );
  }
}

必要なパッケージを追加しておく!
https://pub.dev/packages/auto_route
https://pub.dev/packages/auto_route_generator
https://pub.dev/packages/build_runner

🧷summary

TabをTapすると画面が切り替わるページを3ページ作成しておきます。

1ページ
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

()
class OneScreen extends StatelessWidget {
  const OneScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('OneScreen', style: TextStyle(fontSize: 30, color: Colors.red))),
    );
  }
}
2ページ
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

()
class SecondScreen extends StatelessWidget {
  const SecondScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('SecondScreen', style: TextStyle(fontSize: 30, color: Colors.blue))),
    );
  }
}
3ページ
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';

()
class ThreeScreen extends StatelessWidget {
  const ThreeScreen({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('ThreeScreen', style: TextStyle(fontSize: 30, color: Colors.green))),
    );
  }
}

AutoTabsRouterには、表示したページのクラスを指定する。他のコードは、パッケージのお決まりのコードだと思ってこんな感じで書いてください。MainScreenまで作成したら、build_runnerのコマンドを実行してルートのコードを設定してください。この時点ではまだ画面遷移の設定は終わっていません!

BottomNavigationBarを表示するページ
import 'package:auto_route/auto_route.dart';
import 'package:auto_route_example/root/app_router.gr.dart';
import 'package:flutter/material.dart';

()
class MainScreen extends StatelessWidget {
  const MainScreen({super.key});

  
  Widget build(BuildContext context) {
    /// [AutoTabsRouter]をビルドし、次のように使用する。
    /// ページをレンダリングするための[IndexedStack]を構築する。
    return AutoTabsRouter(
      routes: const [
        OneScreen(),
        SecondScreen(),
        ThreeScreen(),
      ],
      transitionBuilder: (context, child, animation) {
        // 不透明度の遷移を作成します。
        return FadeTransition(opacity: animation, child: child);
      },
      builder: (context, child) {
        // 並列ルーティングを処理するルーター・ウィジェットの実装
        final tabsRouter = AutoTabsRouter.of(context);
        return Scaffold(
          body: child,
          bottomNavigationBar: BottomNavigationBar(
            currentIndex: tabsRouter.activeIndex,
            onTap: (value) {
              tabsRouter.setActiveIndex(value);
            },
            items: const [
              BottomNavigationBarItem(
                icon: Icon(Icons.home),
                label: 'Home',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.feed),
                label: 'feed',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.settings),
                label: 'Settings',
              ),
            ],
          ),
        );
      },
    );
  }
}

ページを作成したら、build_runnerのコマンドを実行する。

flutter pub run build_runner build --delete-conflicting-outputs

BottomNavigationBar用のルートを設定すればTabでページを切り替えることができるようになります。

ルートの設定ファイル
import 'package:auto_route/auto_route.dart';
import 'package:auto_route_example/root/app_router.gr.dart';

// flutter pub run build_runner build --delete-conflicting-outputs
(replaceInRouteName: 'Page,Route')
class AppRouter extends $AppRouter {
  
  List<AutoRoute> get routes => [
    // BottomNavigationBar用のルーティングを指定する
        AutoRoute(page: MainScreen.page,
        initial: true,
        // childrenにはTabBarのページを指定する
        children: [
          AutoRoute(page: OneScreen.page),
          AutoRoute(page: SecondScreen.page),
          AutoRoute(page: ThreeScreen.page),
        ],
        )
      ];
}

main.dartauto_route対応のコードに修正する。

main.dartの設定
import 'package:auto_route_example/root/app_router.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({super.key});

  final _appRouter = AppRouter(); // auto_routeのrouterを追加

  
  Widget build(BuildContext context) {
    return MaterialApp.router( // MaterialApp.routerに変更
      routerConfig: _appRouter.config(), // auto_routeのrouterを追加
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
    );
  }
}

TabをTapするとこんな感じでページが切り替わります。

🧑‍🎓thoughts

今回は、auto_routeでBottomNavigationBarを使ってみました。go_routerが公式だから良い、安心だと思われているようですが、ルートを自動生成してくれたりtype safeに扱えるだとかで人気ある画面遷移のパッケージなので、ご興味ある方は使ってみてください💙💚

完成したコードのブランチのリンク

参考にしたソースコード

参考になった動画:
https://www.youtube.com/watch?v=_HtlQ8SKn8E

Discussion

JboyHashimotoJboyHashimoto

2024/10/12

routeのクラスの設定方法が変わったようです。

https://pub.dev/packages/auto_route/changelog
9.0.0 [Breaking Changes]

BREAKING CHANGE: No Router class will be generated anymore. Instead, you extend RootStackRouter from the auto_route package.

BREAKING CHANGE: Providing return types inside @RoutePage<Type>() is no longer needed. you just provide the type as you push the page.

BREAKING CHANGE: Providing a global route is now done by overriding the guards property inside the router. implementing AutoRouteGuard is no longer supported.

BREAKING CHANGE: AutoRouterConfig.module is removed as it's no longer needed. PageRouteInfos are now self-contained.

For more info read the complete migration guide Migrating to v9

FIX: Fix Aliased types are not generated correctly.
FEAT: You can now create empty shell routes like follows

const BooksTab = EmptyShellRoute('BooksTab');
   context.push(BooksTab());

変更点: Routerクラスは生成されなくなりました。代わりに、auto_routeパッケージからRootStackRouterを拡張します。

変更: @RoutePage<Type>()内で戻り値の型を提供する必要がなくなりました。

BREAKING CHANGE: グローバルルートの提供は、ルーター内のガードプロパティをオーバーライドすることで行われるようになりました。

変更: AutoRouterConfig.moduleは不要になったので削除されました。PageRouteInfosは自己完結型になりました。

詳しくは完全な移行ガイドをお読みください。

修正: エイリアス型が正しく生成されない問題を修正。
機能: 以下のように空のシェルルートを作成できるようになった。

変更前

変更後

import 'package:auto_route/auto_route.dart';
import 'package:worklab_dashboard/core/router.gr.dart';

(replaceInRouteName: 'Screen|Page,Route')
class AppRouter extends RootStackRouter {
  
  RouteType get defaultRouteType => const RouteType.material();

  
  List<AutoRoute> get routes => [
        AutoRoute(page: MainRoute.page, initial: true, children: [
          AutoRoute(page: TimeLine.page),
          AutoRoute(page: SettingRoute.page),
        ])
      ];
}