🍕

【Flutter】BottomNav実装でRiverpod×Hooksに入門

2021/07/29に公開

これはなに

BottomNavigationによるページ切り替えを実装しながら、下記2つのパッケージに入門する記事です。

BottomNavigationとstateを用いて普通に画面切り替えの実装をするやり方が分かっていると読みやすいと思います。分かっていなくても読めるようには心がけました。

完成予定品はこちら。

実装

まず、パッケージをインストールします。下記コードは執筆時のバージョンですので、適宜公式サイトでバージョンを確認するようお願いします。

dependencies:
  flutter:
    sdk: flutter
  flutter_hooks: ^0.17.0
  hooks_riverpod: ^0.14.0+4
警告の例

インストールし終わったら、アプリケーション全体をProviderScopeで包みます。ここにはプロバイダの状態が保存されます。お作法だと思ってください。

void main() {
  runApp(
    ProviderScope(
      child: App(),
    ),
  );
}

次に、BottomNavigationの状態を保持・通知するStateProviderを作成します。enumで用意したいタブの種類を記述し、最初に表示したい画面の値を初期値として渡します。今回は予め作成しておいたHomeScreen() MapScreen() ProfileScreen()の切り替えを行っていきますので、TabTypeもそれに準じた名前をつけました。

enum TabType { home, map, profile }
final tabTypeProvider = StateProvider<TabType>((ref) => TabType.home);

そして、画面全体を格納するScreenContainer()を実装していきます。ここにBottomNavigationを配置し、BottomNavigationの状態にあわせてbodyの中身を切り替えることで画面切り替えを実現しています。

class ScreenContainer extends HookWidget {
  const ScreenContainer({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final tabType = useProvider(tabTypeProvider);
    final _screens = [
      HomeScreen(),
      MapScreen(),
      ProfileScreen(),
    ];

    return Scaffold(
      body: _screens[tabType.state.index],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: tabType.state.index,
        onTap: (int selectIndex) {
          tabType.state = TabType.values[selectIndex];
        },
        selectedItemColor: Colors.black,
        unselectedItemColor: Colors.grey,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.map),
            label: 'map',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'person',
          ),
        ],
      ),
    );
  }
}

肝となる部分をひとつひとつ紹介していきます。

まず、タブの状態をuseProviderhooksで取り出しています。こうすることでtabTapeのstateプロパティにタブの状態が格納されます。

final tabType = useProvider(tabTypeProvider);

画面のリストを作成し、bodyプロパティがリストの中の画面を切り替わるようにします。enumで記述されているtabTypeの各値はインデックスを保持しているため、そのインデックスに対応して画面を表示するようにします。

 final _screens = [
      HomeScreen(),
      MapScreen(),
      ProfileScreen(),
    ];

// ~~~
    body: _screens[tabType.state.index],

そして、各BottomNavigationItemがタップされたときに、それらのインデックスに応じてtabType.stateを切り替えるようにしています。するともちろんtabType.state.indexも変わってくるので、画面の切り替えが行われるというわけです。

 onTap: (int selectIndex) {
   tabType.state = TabType.values[selectIndex];
 },

簡単な説明になりましたが、以上になります。最後に全体像を掲載します。RiverpodとHooksを用いることですっきり書けているつもりです。より良い書き方があれば教えて下さい!

void main() {
  runApp(
    ProviderScope(
      child: App(),
    ),
  );
}

class App extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'app',
      home: ScreenContainer(),
      debugShowCheckedModeBanner: false,
    );
  }
}

enum TabType { home, map, profile }
final tabTypeProvider = StateProvider<TabType>((ref) => TabType.home);

class ScreenContainer extends HookWidget {
  const ScreenContainer({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final tabType = useProvider(tabTypeProvider);
    final _screens = [
      HomeScreen(),
      MapScreen(),
      ProfileScreen(),
    ];

    return Scaffold(
      body: _screens[tabType.state.index],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: tabType.state.index,
        onTap: (int selectIndex) {
          tabType.state = TabType.values[selectIndex];
        },
        selectedItemColor: Colors.black,
        unselectedItemColor: Colors.grey,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.map),
            label: 'map',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'person',
          ),
        ],
      ),
    );
  }
}

その他

こちらの記事が大変参考になりました。ありがとうございました。
https://zuma-lab.com/posts/flutter-bottom-navigation-bar-with-riverpod

また、各種公式Docsも掲載しておきます。
https://riverpod.dev/docs/getting_started
https://pub.dev/documentation/flutter_hooks/latest/

Discussion