👅

RiverpodでTabbarを作ってみた

2023/08/13に公開

RiverpodでTabBarを使う

アプリ開発で画面上にタブを配置することはよくありますよね。今回はTabBarを使ったUIを作るパターンを記事にしてみました。

StatefulWidgetを使ったパターン

https://api.flutter.dev/flutter/material/TabBar-class.html

解説

TabBarを使用する際に、TabControllerを明示的に指定しない場合は、DefaultTabControllerをウィジェットの上層に配置してタブの管理を行うことが必要です。このとき、TabControllerlengthプロパティ(タブの数)は、タブのリストと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