RiverpodでBottomNavigationBarを作る
Riverpodでボトムナビゲーションバーを作る
StateNotifier、Notifier、enumを使いタブがタップされたら、ページを切り替える状態管理をriverpodで行うパターンを作ってみました。
Riverpodを使わない場合は、状態管理はStatefulWidgetのsetStateで行います。タブをタップすると、画面が切り替わるという単純なロジックですね。
🔰StatefulWidgetの場合
公式リファレンスの方法だと、標準WidgetをStatefulWidgetのsetStateで状態管理して使う。これが昔からあるパターンですね。初心者も理解しやすいと思います。
import 'package:flutter/material.dart';
/// Flutter code sample for [BottomNavigationBar].
void main() => runApp(const BottomNavigationBarExampleApp());
class BottomNavigationBarExampleApp extends StatelessWidget {
const BottomNavigationBarExampleApp({super.key});
Widget build(BuildContext context) {
return const MaterialApp(
home: BottomNavigationBarExample(),
);
}
}
class BottomNavigationBarExample extends StatefulWidget {
const BottomNavigationBarExample({super.key});
State<BottomNavigationBarExample> createState() =>
_BottomNavigationBarExampleState();
}
class _BottomNavigationBarExampleState
extends State<BottomNavigationBarExample> {
int _selectedIndex = 0;
static const TextStyle optionStyle =
TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Text(
'Index 0: Home',
style: optionStyle,
),
Text(
'Index 1: Business',
style: optionStyle,
),
Text(
'Index 2: School',
style: optionStyle,
),
];
void _onItemTapped(int index) {
// setStateでタブがタップされたら、画面を更新する
setState(() {
_selectedIndex = index;
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
🔨ConsumerWidgetを使ったパターン
StatefulWidgetを使わないパターンですね。画面が切り替わるロジックは、Riverpodがやってくれます。
複雑な状態管理を行うときは、StateNotifierを使用する。StateProviderを使ったパターンもあるが、こちらを使った方が、複雑なコードを書かないので楽に状態管理ができる。
🔨StateNotifierで状態管理
Riverpod2.0では、非推奨ですが、比較対象としてサンプル作りました。世の中にサンプルあるのですが、説明がわかりにくいな〜と思い自分が理解できる書き方ですが作ってみました。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// [StateNotifierの場合]
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
enum PageType {
Home,
Settings,
}
class PageNotifier extends StateNotifier<PageType> {
PageNotifier() : super(PageType.Home);// 最初に表示するページを設定
// タブが切り替わった時に呼ばれる
void changePage(PageType pageType) {
state = pageType;
}
}
final pageProvider = StateNotifierProvider<PageNotifier, PageType>(
(ref) => PageNotifier(),
);
class MyApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
title: 'Riverpod BottomNavigationBar',
theme: ThemeData(primarySwatch: Colors.blue),
home: MainPage(),
);
}
}
class MainPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final currentPage = ref.watch(pageProvider);
return Scaffold(
appBar: AppBar(
title: Text(currentPage == PageType.Home ? "Home" : "Settings"),
),
body: currentPage == PageType.Home ? HomeWidget() : SettingsWidget(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentPage == PageType.Home ? 0 : 1,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
onTap: (index) {
final pageType = index == 0 ? PageType.Home : PageType.Settings;
ref.read(pageProvider.notifier).changePage(pageType);
},
),
);
}
}
class HomeWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Text('This is Home Page!'),
);
}
}
class SettingsWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Text('This is Settings Page!'),
);
}
}
🔨Notifierを使ったパターン
Riverpod2.0からは、NotifierをStateNotifierとStateProviderの代わりに使うことが推奨されました。ですので、作ってみました。これを使った例は今の所記事でも見つけられていないです。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// [Notifierの場合]
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
enum PageType {
Home,
Settings,
}
class BottomNavigationNotifier extends Notifier<PageType> {
build() {
return PageType.Home;// 最初に表示するページを設定
}
// タブが切り替わった時に呼ばれる
void changePage(PageType pageType) {
state = pageType;
}
}
final bottomNavigationNotifierProvider = NotifierProvider<BottomNavigationNotifier, PageType>(BottomNavigationNotifier.new);
class MyApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
title: 'Riverpod BottomNavigationBar',
theme: ThemeData(primarySwatch: Colors.blue),
home: MainPage(),
);
}
}
class MainPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final currentPage = ref.watch(bottomNavigationNotifierProvider);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.indigo,
title: Text(currentPage == PageType.Home ? "Home" : "Settings"),
),
body: currentPage == PageType.Home ? HomeWidget() : SettingsWidget(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentPage == PageType.Home ? 0 : 1,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
onTap: (index) {
final pageType = index == 0 ? PageType.Home : PageType.Settings;
ref.read(bottomNavigationNotifierProvider.notifier).changePage(pageType);
},
),
);
}
}
class HomeWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Text('This is Home Page Notifier!'),
);
}
}
class SettingsWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Text('This is Settings Page Notifier!'),
);
}
}
まとめ
StatefulWidgetの時は、static const リストの変数を定義して、タップしたら、ページを切り替えるメソッドが実行されて、状態管理はsetStateで行っています。
Riverpodの時は、enumでどこのページに切り替えるのか指定して、StateNotifierかNotifierでタブが切り替わる状態を管理しています。
おまけ
ページ3個以上に増やすときはこんな感じで書けばできます。
import 'package:chart_app/screen/page/chart/line_chart.dart';
import 'package:chart_app/screen/page/home/home_page.dart';
import 'package:chart_app/screen/page/info/InfoPage.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
enum PageType {
Home,
Chart,
Info
}
class BottomNavigationNotifier extends Notifier<PageType> {
build() {
return PageType.Home; // 最初に表示するページを設定
}
// タブが切り替わった時に呼ばれる
void changePage(PageType pageType) {
state = pageType;
}
}
final bottomNavigationNotifierProvider = NotifierProvider<BottomNavigationNotifier, PageType>(BottomNavigationNotifier.new);
class BottomNavigationPage extends ConsumerWidget {
const BottomNavigationPage({super.key});
static const String relativePath = '/bottom_navigation';
Widget build(BuildContext context, WidgetRef ref) {
final currentPage = ref.watch(bottomNavigationNotifierProvider);
String appBarTitle;
Widget bodyWidget;
switch(currentPage) {
case PageType.Home:
appBarTitle = "Home";
bodyWidget = const HomePage();
break;
case PageType.Chart:
appBarTitle = "Chart";
bodyWidget = const LineChartPage();
break;
case PageType.Info:
appBarTitle = "Info";
bodyWidget = const InfoPage();
break;
}
return Scaffold(
body: bodyWidget,
bottomNavigationBar: BottomNavigationBar(
currentIndex: PageType.values.indexOf(currentPage),
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.show_chart),
label: 'Chart',
),
BottomNavigationBarItem(
icon: Icon(Icons.info),
label: 'Info',
),
],
onTap: (index) {
final pageType = PageType.values[index];
ref.read(bottomNavigationNotifierProvider.notifier).changePage(pageType);
},
),
);
}
}
Discussion