BottomNavigationBar を flutter_riverpod で制御してみた
やってみたらできました
TL;DR
- ほぼこの記事のコードと同じです
- flutter で Stateless な BottomNavigationBar を作る記事がよかったのでやってみた | 北山淳也 | zenn
- さらにその元記事
- Flutter: シンプルでStatelessなBottomNavigationBarを作ってみた話|こんぶ|note
CLI でプロジェクト作成
ターミナルで
flutter create myapp
cd myapp
# iOSシミュレーターが起動してなければ起動させる
open -a Simulator
# コンパイルして実行。flutter create しただけの状態でも可
flutter run
出来上がりのイメージ
最終的なファイル一覧
必要なパッケージを入れる
今回は追加で導入するパッケージは2つです。
- pedantic_mono
- 強めの linter
- pedantic_mono | pub.dev
- Dart/Flutter の静的解析強化のススメ. プロジェクトには analysis_options.yaml… | by mono | Flutter 🇯🇵 | Medium
- 強めの linter
- flutter_riverpod
- 状態管理パッケージ
- flutter_riverpod | pub.dev
pubspec.yaml をこんな感じにします。
書き換えたあと flutter pub get
するのが正式な方法ですが、
VSCode に flutter 拡張機能入れてあればファイルを編集した時点で自動で flutter pub get
してくれます。
name: myApp
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.0
flutter_riverpod: ^0.12.2
dev_dependencies:
pedantic_mono: any
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
pedantic_mono 用の analysis_options.yaml を用意する
ルートディレクトリに analysis_options.yaml を用意しておきます。
これで lint が効くようになります。
touch analysis_options.yaml
include: package:pedantic_mono/analysis_options.yaml
フッター切り替え時の画面を作る
ここではアイコンを中央に配置してるだけの画面を作ります。
mkdir -p lib/views/screens
touch lib/views/screens/first_page.dart
touch lib/views/screens/second_paage.dart
import 'package:flutter/material.dart';
class FirstPage extends StatelessWidget {
Widget build(BuildContext context) {
return const Scaffold(
body: const Center(
child: const Icon(
Icons.face,
size: 200,
),
),
);
}
}
import 'package:flutter/material.dart';
class SecondPage extends StatelessWidget {
Widget build(BuildContext context) {
return const Scaffold(
body: const Center(
child: const Icon(
Icons.fastfood,
size: 200,
),
),
);
}
}
BottomNavigationBar をもった StatelessWidget を定義する
前回の記事 では ChangeNotifier
を継承した Notifier クラスを用意しましたが、
今回は Riverpod の StateProvider
を使います。
タブの選択値程度ならクラスを用意するまでもないのでenumを直接監視させちゃいましょう。
Provider を global に置いちゃうのが Riverpod の面白いところですね。
import 'package:flutter_riverpod/flutter_riverpod.dart';
final pageTypeProvider = StateProvider<PageType>((ref) => PageType.first);
enum PageType {
first,
second,
}
小見出しに「BottomNavigationBar をもった StatelessWidget を定義する」と書きまして
実際にそう書くのですが、
Widget 配下で使っている Riverpod の Consumer は StatefulWidget
を継承した物なので
もうこれ何をやりたいのかわかんねぇな?
(とはいえ State の管理を Riverpod に任せて隠蔽できるので直接 StatefulWidget
を使うのとは管理コストが全然違いますね)
というわけでこの部分のコード全体はこんな感じになります。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'first_page.dart';
import 'second_paage.dart';
final pageTypeProvider = StateProvider<PageType>((ref) => PageType.first);
enum PageType {
first,
second,
}
class RootPage extends StatelessWidget {
final List<Widget> _pageList = <Widget>[
FirstPage(),
SecondPage(),
];
Widget build(BuildContext context) {
return Consumer(
builder: (context, watch, child) {
final pageType = watch(pageTypeProvider);
final tabItems = [
const BottomNavigationBarItem(
icon: Icon(Icons.face),
label: '',
),
const BottomNavigationBarItem(
icon: Icon(Icons.fastfood),
label: '',
),
];
return Scaffold(
body: _pageList[pageType.state.index],
bottomNavigationBar: BottomNavigationBar(
currentIndex: pageType.state.index,
onTap: (index) {
pageType.state = PageType.values[index];
},
items: tabItems,
),
);
},
);
}
}
Dart の enum のIndex値は index プロパティで取得できます。
逆に、int を enum にするには values の index を指定してやればできます。
- Dartでenumが使えるようになりました|NEWS|株式会社INDETAIL(インディテール)
- Flutter / Dart Convert Int to Enum | Stack Overflow
- Enumerated types | Language tour | Dart
runApp() で実行する StatelessWidget をつくる
最後に View のエントリポイントをつくります。
Riverpod の Provider を監視するために Widget を ProviderScope()
で包んでやります。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'views/screens/root_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(),
},
),
),
darkTheme: ThemeData(brightness: Brightness.dark),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (_) => RootPage(),
},
),
);
}
}
ここまでできたら flutter run することで
前述の 出来上がりのイメージ に出したような画面が実行できるはずです。
おつかれさまでした。
今回のリポジトリはこちらです。
参考
- Riverpod
- lohanidamodar/fl_live_launcher: Let's make a android launcher app using Flutter | GitHub
Discussion