🪠

go router builderでBottomNavigationBarを使う

2023/11/05に公開

Overview

go router builderを使用して、BottomNavigationBarとTabBarを使う方法を知らなかったもので、公式のGithubにサンプルコードがあるだけで、こちらを動かしながら、学習するしかなかったので、ソースコードをそのまま使って動かしてみて、少し書き変えて使ってみて仕組みを理解できました。

summary

ここにあるサンプルをそのまま使うだけで、パッケージが用意してくれているナビゲーションを使うことができます。
https://github.com/flutter/packages/tree/main/packages/go_router_builder/example/lib

全体のコード
main.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

part 'main.g.dart';

const textTitle =
    TextStyle(fontSize: 30, color: Colors.black54, fontWeight: FontWeight.bold);

void main() => runApp(App());

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

  
  Widget build(BuildContext context) => MaterialApp.router(
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        routerConfig: _router,
      );

  final GoRouter _router = GoRouter(
    routes: $appRoutes,
    initialLocation: '/home',
  );
}

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

  
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text('foo')),
      );
}

/// [BottomNavigationBarの画面遷移のルート]
<MainShellRouteData>(
  branches: <TypedStatefulShellBranch<StatefulShellBranchData>>[
    TypedStatefulShellBranch<HomeShellBranchData>(
      routes: <TypedRoute<RouteData>>[
        TypedGoRoute<HomeRouteData>(
          path: '/home',
        ),
      ],
    ),
    TypedStatefulShellBranch<NotificationsShellBranchData>(
      routes: <TypedRoute<RouteData>>[
        TypedGoRoute<NotificationsRouteData>(
          path: '/notifications/:section',
        ),
      ],
    ),
    TypedStatefulShellBranch<OrdersShellBranchData>(
      routes: <TypedRoute<RouteData>>[
        TypedGoRoute<OrdersRouteData>(
          path: '/orders',
        ),
      ],
    ),
  ],
)

/// [BottomNavigationBarの画面遷移のルート]
class MainShellRouteData extends StatefulShellRouteData {
  const MainShellRouteData();

  
  Widget builder(
    BuildContext context,
    GoRouterState state,
    StatefulNavigationShell navigationShell,
  ) {
    return MainPageView(
      navigationShell: navigationShell,
    );
  }
}

class HomeShellBranchData extends StatefulShellBranchData {
  const HomeShellBranchData();
}

class NotificationsShellBranchData extends StatefulShellBranchData {
  const NotificationsShellBranchData();

  static String $initialLocation = '/notifications/old';
}

class OrdersShellBranchData extends StatefulShellBranchData {
  const OrdersShellBranchData();
}

class HomeRouteData extends GoRouteData {
  const HomeRouteData();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const HomePage();
  }
}

/// [通常のの画面遷移のルート]
<NextRoute>(
  path: '/next',
)
class NextRoute extends GoRouteData {
  const NextRoute();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const NextPage();
  }
}

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

  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey[100],
      child: Column(
        children: [
          // 画面中央に寄せる設定
          SizedBox(height: MediaQuery.of(context).size.height * 0.3),
          // このページのマイン画面を作成
          const Text('Home page', style: textTitle),
          const SizedBox(height: 20),
          TextButton(
              onPressed: () {
                const NextRoute().push(context);
              },
              child: const Text('Stackが残る画面遷移')),
          const SizedBox(height: 20),
          TextButton(
              onPressed: () {
                const NextRoute().go(context);
              },
              child: const Text('Stackが削除される画面遷移')),
        ],
      ),
    );
  }
}

/// [TabBarで使用するenum]
enum NotificationsPageSection {
  latest,
  old,
  archive,
}

class NotificationsRouteData extends GoRouteData {
  const NotificationsRouteData({
    required this.section,
  });
  // enumを受け取る
  final NotificationsPageSection section;

  
  Widget build(BuildContext context, GoRouterState state) {
    return NotificationsPageView(
      section: section,
    );
  }
}

class OrdersRouteData extends GoRouteData {
  const OrdersRouteData();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const OrdersPageView(label: 'メッセージのやり取り');
  }
}

class MainPageView extends StatelessWidget {
  const MainPageView({
    required this.navigationShell,
    super.key,
  });

  final StatefulNavigationShell navigationShell;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: navigationShell,
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'ホーム',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.notifications),
            label: '通知',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            label: 'メッセージ',
          ),
        ],
        currentIndex: navigationShell.currentIndex,
        onTap: (int index) => _onTap(context, index),
      ),
    );
  }

  void _onTap(BuildContext context, int index) {
    navigationShell.goBranch(
      index,
      initialLocation: index == navigationShell.currentIndex,
    );
  }
}

class HomePageView extends StatelessWidget {
  const HomePageView({
    required this.label,
    super.key,
  });

  final String label;

  
  Widget build(BuildContext context) {
    return Center(
      child: Text(label),
    );
  }
}

class NotificationsPageView extends StatelessWidget {
  const NotificationsPageView({
    super.key,
    required this.section,
  });

  final NotificationsPageSection section;

  
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      initialIndex: NotificationsPageSection.values.indexOf(section),
      child: const Column(
        children: <Widget>[
          TabBar(
            tabs: <Tab>[
              Tab(
                child: Text(
                  ' ヒマしてる',
                  style: TextStyle(color: Colors.black87),
                ),
              ),
              Tab(
                child: Text(
                  '友達募集',
                  style: TextStyle(color: Colors.black87),
                ),
              ),
              Tab(
                child: Text(
                  '恋人募集',
                  style: TextStyle(color: Colors.black87),
                ),
              ),
            ],
          ),
          Expanded(
            child: TabBarView(children: <Widget>[
              ParamsPage(label: '🏠ホームページです'),
              ParamsPage(label: '🔔通知ページです'),
              ParamsPage(label: '📝メッセージページです'),
            ]),
          ),
        ],
      ),
    );
  }
}

class OrdersPageView extends StatelessWidget {
  const OrdersPageView({
    required this.label,
    super.key,
  });

  final String label;

  
  Widget build(BuildContext context) {
    return Center(
      child: Text(label),
    );
  }
}

/// [TabBarViewで使用するWidget] 本当は3ページ用意すべき
class ParamsPage extends StatelessWidget {
  const ParamsPage({
    required this.label,
    super.key,
  });

  final String label;

  
  Widget build(BuildContext context) {
    return Center(
      child: Text(label),
    );
  }
}

/// [画面遷移するページ]
class NextPage extends StatelessWidget {
  const NextPage({
    super.key,
  });

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            // 画面中央に寄せる設定
            SizedBox(height: MediaQuery.of(context).size.height * 0.3),
            const Text('Next page', style: textTitle),
            const SizedBox(height: 20),
            TextButton(
                onPressed: () {
                  /// [Stackが残る画面遷移]をすれば、戻るボタンで戻れる
                  context.pop();
                },
                child: const Text('戻る')),
          ],
        ),
      ),
    );
  }
}

ソースコードの解説

今回は、main関数に、ルーターの設定をします。
routes: $appRoutesは、自動生成されたファイルを読み込むコードです。initialLocationは、最初に表示するページを指定します。

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

  
  Widget build(BuildContext context) => MaterialApp.router(
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        routerConfig: _router,
      );

  final GoRouter _router = GoRouter(
    routes: $appRoutes,
    initialLocation: '/home',
  );
}

BottomNavigationBarの設定
タブメニューをタップする画面遷移するルートの設定をする。通常の画面遷移のコードは別の場所に書きます。

/// [BottomNavigationBarの画面遷移のルート]
<MainShellRouteData>(
  branches: <TypedStatefulShellBranch<StatefulShellBranchData>>[
    TypedStatefulShellBranch<HomeShellBranchData>(
      routes: <TypedRoute<RouteData>>[
        TypedGoRoute<HomeRouteData>(
          path: '/home',
        ),
      ],
    ),
    TypedStatefulShellBranch<NotificationsShellBranchData>(
      routes: <TypedRoute<RouteData>>[
        TypedGoRoute<NotificationsRouteData>(
          path: '/notifications/:section',
        ),
      ],
    ),
    TypedStatefulShellBranch<OrdersShellBranchData>(
      routes: <TypedRoute<RouteData>>[
        TypedGoRoute<OrdersRouteData>(
          path: '/orders',
        ),
      ],
    ),
  ],
)

/// [BottomNavigationBarの画面遷移のルート]
class MainShellRouteData extends StatefulShellRouteData {
  const MainShellRouteData();

  
  Widget builder(
    BuildContext context,
    GoRouterState state,
    StatefulNavigationShell navigationShell,
  ) {
    return MainPageView(
      navigationShell: navigationShell,
    );
  }
}

class HomeShellBranchData extends StatefulShellBranchData {
  const HomeShellBranchData();
}

class NotificationsShellBranchData extends StatefulShellBranchData {
  const NotificationsShellBranchData();

  static String $initialLocation = '/notifications/old';
}

class OrdersShellBranchData extends StatefulShellBranchData {
  const OrdersShellBranchData();
}

class HomeRouteData extends GoRouteData {
  const HomeRouteData();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const HomePage();
  }
}

/// [通常の画面遷移のルート]
<NextRoute>(
  path: '/next',
)
class NextRoute extends GoRouteData {
  const NextRoute();

  
  Widget build(BuildContext context, GoRouterState state) {
    return const NextPage();
  }
}

画面はこんな感じになってます
某有名なマッチングアプリのタブを参考にしてみました笑

thoughts

今回は、go_router_builder公式のソースコードを参考に学習をやってみました!
今回のように情報が少ないパッケージを使うときは、公式のGithubのソースコードを動かしながら、学習するしかないので、慣れていないと苦労する部分がありました。
go_router_builderで、タブメニューを作りたい人は参考にしてみてください❤️

Jboy王国メディア

Discussion