🫶

RiverpodでNavigationRailを使ってみる

2023/08/13に公開

NavigationRailを使う

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

Flutterの標準Widgetでサイドバーを作れるWidgetがあります。それがNavigationRailです。モバイルよりもFlutter Webで使うイメージがあります。スマートフォンでサイドバーっていうのはない気がするので😅

こんな感じでサイドバーを作れます

🔰StatefulWidgetで作ったパターン

調べてみると、これしか情報が出てこない。あまり使われていないんですかね。Flutter Web以外にもデスクトップでも使っていそうですね。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Navigation Rail Sample',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;
  static const List<Widget> _widgetOptions = <Widget>[
    HomeWidget(),
    AboutWidget(),
    SettingsWidget(),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Navigation Rail Sample'),
      ),
      body: Row(
        children: <Widget>[
          NavigationRail(
            selectedIndex: _selectedIndex,
            onDestinationSelected: (int index) {
              setState(() {
                _selectedIndex = index;
              });
            },
            destinations: const <NavigationRailDestination>[
              NavigationRailDestination(
                icon: Icon(Icons.home),
                label: Text('Home'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.info),
                label: Text('About'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.settings),
                label: Text('Settings'),
              ),
            ],
          ),
          VerticalDivider(thickness: 1, width: 1),
          // This is the main content.
          Expanded(
            child: Center(
              child: _widgetOptions.elementAt(_selectedIndex),
            ),
          )
        ],
      ),
    );
  }
}

class HomeWidget extends StatelessWidget {
  const HomeWidget({super.key});

  
  Widget build(BuildContext context) {
    return Text('Home Page');
  }
}

class AboutWidget extends StatelessWidget {
  const AboutWidget({super.key});

  
  Widget build(BuildContext context) {
    return Text('About Page');
  }
}

class SettingsWidget extends StatelessWidget {
  const SettingsWidget({super.key});

  
  Widget build(BuildContext context) {
    return Text('Settings Page');
  }
}

Riverpodを使用したパターン

Riverpodを使用して、状態管理をしたパターンを考えてみました。サイドバーのタブをタップすると、画面が切り替わります。
動作はこんな感じです

StateNotifierを使ったパターン

Riverpod2.0では非推奨ですが、まだまだ使われているようで、作ってみました。タブの切り替えるロジックは、enumとStateNotifierで行っています。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() => runApp(ProviderScope(child: MyApp()));

enum NavigationSection {
  Home,
  About,
  Settings,
}

class NavigationNotifier extends StateNotifier<NavigationSection> {
  NavigationNotifier() : super(NavigationSection.Home);

  void switchSection(NavigationSection section) {
    state = section;
  }
}

final navigationProvider = StateNotifierProvider<NavigationNotifier, NavigationSection>(
  (ref) => NavigationNotifier(),
);

class MyApp extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: 'Navigation Rail with Riverpod',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const NavigationRailPage(),
    );
  }
}

class NavigationRailPage extends ConsumerWidget {
  const NavigationRailPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final section = ref.watch(navigationProvider);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.amber,
        title: const Text('Navigation Rail with Riverpod'),
      ),
      body: Row(
        children: <Widget>[
          NavigationRail(
            selectedIndex: section.index,
            onDestinationSelected: (int index) {
              ref.read(navigationProvider.notifier).switchSection(NavigationSection.values[index]);
            },
            destinations: const <NavigationRailDestination>[
              NavigationRailDestination(
                icon: Icon(Icons.home),
                label: Text('Home'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.info),
                label: Text('About'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.settings),
                label: Text('Settings'),
              ),
            ],
          ),
          const VerticalDivider(thickness: 1, width: 1),
          Expanded(
            child: Center(
              child: _widgetForSection(section),
            ),
          ),
        ],
      ),
    );
  }

  Widget _widgetForSection(NavigationSection section) {
    switch (section) {
      case NavigationSection.Home:
        return const HomeWidget();
      case NavigationSection.About:
        return const AboutWidget();
      case NavigationSection.Settings:
        return const SettingsWidget();
    }
  }
}

class HomeWidget extends StatelessWidget {
  const HomeWidget({super.key});

  
  Widget build(BuildContext context) {
    return const Text('Home Page');
  }
}

class AboutWidget extends StatelessWidget {
  const AboutWidget({super.key});

  
  Widget build(BuildContext context) {
    return const Text('About Page');
  }
}

class SettingsWidget extends StatelessWidget {
  const SettingsWidget({super.key});

  
  Widget build(BuildContext context) {
    return const Text('Settings Page');
  }
}

Notifierを使ったパターン

Riverpod2.0から推奨されるようになった状態管理をする機能。StateNotifierとStateProviderが非推奨になったので、こちらで状態管理を行います。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() => runApp(ProviderScope(child: MyApp()));

enum NavigationSection {
  Home,
  About,
  Settings,
}

class NavigationNotifier extends Notifier<NavigationSection> {

  
   build() {
    return NavigationSection.Home;// 最初に表示するページを設定
  }
  // サイドバーのタブを押したときに呼ばれる
  void switchSection(NavigationSection section) {
    state = section;
  }
}

final navigationProvider = NotifierProvider<NavigationNotifier,NavigationSection >(NavigationNotifier.new);

class MyApp extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: 'Navigation Rail with Riverpod',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const NavigationRailPage(),
    );
  }
}

class NavigationRailPage extends ConsumerWidget {
  const NavigationRailPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final section = ref.watch(navigationProvider);

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.red,
        title: const Text('Navigation Rail with Riverpod'),
      ),
      body: Row(
        children: <Widget>[
          NavigationRail(
            selectedIndex: section.index,
            onDestinationSelected: (int index) {
              ref.read(navigationProvider.notifier).switchSection(NavigationSection.values[index]);
            },
            destinations: const <NavigationRailDestination>[
              NavigationRailDestination(
                icon: Icon(Icons.home),
                label: Text('Home'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.info),
                label: Text('About'),
              ),
              NavigationRailDestination(
                icon: Icon(Icons.settings),
                label: Text('Settings'),
              ),
            ],
          ),
          const VerticalDivider(thickness: 1, width: 1),
          Expanded(
            child: Center(
              child: _widgetForSection(section),
            ),
          ),
        ],
      ),
    );
  }

  Widget _widgetForSection(NavigationSection section) {
    switch (section) {
      case NavigationSection.Home:
        return const HomeWidget();
      case NavigationSection.About:
        return const AboutWidget();
      case NavigationSection.Settings:
        return const SettingsWidget();
    }
  }
}

class HomeWidget extends StatelessWidget {
  const HomeWidget({super.key});

  
  Widget build(BuildContext context) {
    return const Text('Home Page');
  }
}

class AboutWidget extends StatelessWidget {
  const AboutWidget({super.key});

  
  Widget build(BuildContext context) {
    return const Text('About Page');
  }
}

class SettingsWidget extends StatelessWidget {
  const SettingsWidget({super.key});

  
  Widget build(BuildContext context) {
    return const Text('Settings Page');
  }
}

まとめ

最近、仕事でサイドバーを作ることがありました。
Flutter Webを使用して管理画面を作るときには、画面を切り替えるのに便利な機能だと思いました。仕事ではgo_routerを使っているのですが、使わずにタブの切り替えをしている人もいるようなので、趣味で作ってみました。引き出しが多い方が選択肢が広がると思うので、いろいろなパターンのWidgetを作ってみたいと思います。

Discussion