🪠
go router builderでBottomNavigationBarを使う
Overview
go router builderを使用して、BottomNavigationBarとTabBarを使う方法を知らなかったもので、公式のGithubにサンプルコードがあるだけで、こちらを動かしながら、学習するしかなかったので、ソースコードをそのまま使って動かしてみて、少し書き変えて使ってみて仕組みを理解できました。
summary
ここにあるサンプルをそのまま使うだけで、パッケージが用意してくれているナビゲーションを使うことができます。
全体のコード
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で、タブメニューを作りたい人は参考にしてみてください❤️
Discussion