🫶
RiverpodでNavigationRailを使ってみる
NavigationRailを使う
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