RiverpodでTabbarを作ってみた
RiverpodでTabBarを使う
アプリ開発で画面上にタブを配置することはよくありますよね。今回はTabBarを使ったUIを作るパターンを記事にしてみました。
StatefulWidgetを使ったパターン
解説
TabBarを使用する際に、TabController
を明示的に指定しない場合は、DefaultTabController
をウィジェットの上層に配置してタブの管理を行うことが必要です。このとき、TabController
のlength
プロパティ(タブの数)は、タブのリストとTabBarView
の子ウィジェットの数と一致していなければなりません。
また、TabBar
を使う場面で、そのウィジェットの上層にMaterial
ウィジェットが存在することが必要です。
もし、アプリケーションでTabBarTheme
が設定されている場合、そのテーマの設定がTabBar
に自動的に適用されます。
エラーが発生することがある?
例外が発生しました
FlutterError (No TabController for TabBar.
When creating a TabBar, you must either provide an explicit TabController using the "controller" property, or you must ensure that there is a DefaultTabController above the TabBar.
In this case, there was neither an explicit controller nor a default controller.)
この問題を解決するためには、TabControllerを明示的に提供するか、DefaultTabControllerを使って自動的にTabControllerを提供する必要があります。
このサンプルでは大丈夫なので使ってないです。
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'TabBar Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
TabController? _tabController;
void initState() {
// タブの切り替えを管理するコントローラーを初期化
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TabBar Example'),
bottom: TabBar(// タブバーを作成
controller: _tabController,
tabs: const [
Tab(icon: Icon(Icons.rss_feed)),
Tab(icon: Icon(Icons.local_car_wash)),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
FeedWidget(),
CarWidget(),
],
),
);
}
void dispose() {
// コントローラーを破棄
_tabController?.dispose();
super.dispose();
}
}
class FeedWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Text('This is the Feed Page!'),
);
}
}
class CarWidget extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Text('This is the Car Page!'),
);
}
}
StateNotifierを使ったパターン
DefaultTabControllerをTabBarExampleの親として追加し、TabBarViewをScaffoldのbodyに追加しています。この変更により、TabBarとTabBarViewが正しく動作するようになります。
TabBarはあまり使ったことがないので、時々このエラーでハマります。
こちらがRiverpodで状態管理をしたコード。enumを使ってページの切り替えを行ってます。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() => runApp(ProviderScope(child: MyApp()));
enum TabType {
Feed,
Car,
}
class TabNotifier extends StateNotifier<TabType> {
TabNotifier() : super(TabType.Feed);
void switchTab(TabType tab) {
state = tab;
}
}
final tabProvider = StateNotifierProvider<TabNotifier, TabType>(
(ref) => TabNotifier(),
);
class MyApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
title: 'TabBar with Riverpod',
theme: ThemeData(primarySwatch: Colors.blue),
home: const DefaultTabController(
length: 2, // number of tabs
child: TabBarExample(),
),
);
}
}
class TabBarExample extends ConsumerWidget {
const TabBarExample({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final currentTab = ref.watch(tabProvider);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
title: Text('TabBar with Riverpod'),
bottom: TabBar(
tabs: const [
Tab(icon: Icon(Icons.rss_feed)),
Tab(icon: Icon(Icons.directions_car)),
],
onTap: (index) {
ref.read(tabProvider.notifier).switchTab(TabType.values[index]);
},
),
),
body: TabBarView(
children: [
const FeedWidget(),
const CarWidget(),
],
),
);
}
}
class FeedWidget extends StatelessWidget {
const FeedWidget({super.key});
Widget build(BuildContext context) {
return Center(
child: Text('This is the Feed Page!'),
);
}
}
class CarWidget extends StatelessWidget {
const CarWidget({super.key});
Widget build(BuildContext context) {
return Center(
child: Text('This is the Car Page!'),
);
}
}
Notifierを使ったパターン
こちらもStateNotifierとやっていることは同じです。enumを使って、タブを押すとページが切り替わるロジックになっております。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() => runApp(ProviderScope(child: MyApp()));
enum TabType {
Feed,
Car,
}
class TabNotifier extends Notifier<TabType> {
build() {
return TabType.Feed;// 最初に表示するタブ
}
// タブを押すとページを切り替えるメソッド
void switchTab(TabType tab) {
state = tab;
}
}
final tabProvider = NotifierProvider<TabNotifier, TabType>(TabNotifier.new);
class MyApp extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp(
title: 'TabBar with Riverpod',
theme: ThemeData(primarySwatch: Colors.blue),
home: const DefaultTabController(
length: 2, // number of tabs
child: TabBarExample(),
),
);
}
}
class TabBarExample extends ConsumerWidget {
const TabBarExample({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final currentTab = ref.watch(tabProvider);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orangeAccent,
title: Text('TabBar with Riverpod'),
bottom: TabBar(
tabs: const [
Tab(icon: Icon(Icons.rss_feed)),
Tab(icon: Icon(Icons.directions_car)),
],
onTap: (index) {
ref.read(tabProvider.notifier).switchTab(TabType.values[index]);
},
),
),
body: TabBarView(
children: [
const FeedWidget(),
const CarWidget(),
],
),
);
}
}
class FeedWidget extends StatelessWidget {
const FeedWidget({super.key});
Widget build(BuildContext context) {
return Center(
child: Text('This is the Feed Page!'),
);
}
}
class CarWidget extends StatelessWidget {
const CarWidget({super.key});
Widget build(BuildContext context) {
return Center(
child: Text('This is the Car Page!'),
);
}
}
まとめ
タブバーは使ったことがあまりなかったので、状態管理をしてみて良い勉強になりました。アプリではよく見かけるUIなので、皆さんも個人開発をするときは、参考にして使ってみてください。
Discussion