flutter で Stateless な BottomNavigationBar を作る記事がよかったのでやってみた
久しぶりに flutter 触ったら色々忘れていたので復習も兼ねて。
環境は Intel Mac (Big Sur 11.1) です。
TL;DR
- ほぼこの記事に書いてあることをやっただけです
- Flutter: シンプルでStatelessなBottomNavigationBarを作ってみた話|こんぶ|note
- flutter 導入のあたりはこちらを
- Flutter の始め方メモとサンプルアプリのコメント部分翻訳 | 北山淳也 | zenn
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
-
provider
- 状態管理パッケージ
- provider | 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
provider: ^4.3.2+3
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,
),
),
);
}
}
フッター切り替え状態管理用の class を用意する
標準パッケージの変更通知APIを提供する ChangeNotifier
を継承した class を定義します。
provider
パッケージなどはこの標準パッケージの ChangeNotifier
などを利用して
状態管理を実現しているんですね。
- ChangeNotifier class | foundation library | Dart API
特に難しい実装はないです。
setter と getter を定義し setter を使ったときに状態変化通知を行います。
今回のプロジェクトではMVVMのViewModelを意識した view_models というディレクトリをきっていますが
ディレクトリ構成はなんでもいいと思います。
(なんでもいい、が一番困るのはわかります笑)
mkdir lib/view_models
touch lib/view_models/bottom_navigation_model.dart
import 'package:flutter/material.dart';
class BottomNavigationModel extends ChangeNotifier {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
set currentIndex(int index) {
_currentIndex = index;
notifyListeners();
}
}
BottomNavigationBar をもった StatelessWidget を定義する
前述の ChangeNotifier
を継承した class
(= 今回は BottomNavigationModel
) を利用して
provider
パッケージの ChangeNotifierProvider<T>
と Consumer<T>
を使った
StatelessWidget を定義します。
BottomNavigationBar
と BottomNavigationBarItem
でフッタを構成し、
フッター切り替え時の画面がそれぞれ body
に呼び出されています。
ChangeNotifierProvider<T>
が BottomNavigationModel
の監視を開始して
Consumer<T>
が状態の変更を builder
に通知することで
StatelessWidget
でも状態変化による再描画が実行されるんですね。
- ChangeNotifierProvider class | provider library | Dart API
- Consumer class | provider library | Dart API
- Simple app state management | Flutter
今回の記述するコード自体はシンプルです。
provider
パッケージがよしなにやってくれる部分が大きいからですね。
touch lib/views/screens/root_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../view_models/bottom_navigation_model.dart';
import 'first_page.dart';
import 'second_paage.dart';
class RootPage extends StatelessWidget {
final List<Widget> _pageList = <Widget>[
FirstPage(),
SecondPage(),
];
Widget build(BuildContext context) {
return ChangeNotifierProvider<BottomNavigationModel>(
create: (_) => BottomNavigationModel(),
child: Consumer<BottomNavigationModel>(
builder: (context, model, child) {
final tabItems = [
const BottomNavigationBarItem(
icon: Icon(Icons.face),
label: '',
),
const BottomNavigationBarItem(
icon: Icon(Icons.fastfood),
label: '',
),
];
return Scaffold(
body: _pageList[model.currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: model.currentIndex,
onTap: (index) {
model.currentIndex = index;
},
items: tabItems,
),
);
},
),
);
}
}
runApp() で実行する StatelessWidget をつくる
最後に View のエントリポイントをつくります。
こういうサンプルアプリの記事では
エントリポイントの MaterialApp
の home
に直接 Widget
を構築するものが多いのですが
私は initialRoute
と routes
を定義して
ベースとなる Widget
を呼び出すような作りにしておくことが多いです。
後から何かと改修しやすいのでこうすることが多いですね。
(ベースにする MaterialApp
にはテーマの情報やページ切り替え時のテーマを指定するので
それ以外の情報を書きたくないのも理由です)
import 'package:flutter/material.dart';
import 'views/screens/root_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return 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
することで
前述の 出来上がりのイメージ に出したような画面が実行できるはずです。
おつかれさまでした。
今回のリポジトリはこちらです。
参考
- Linter for Dart
- Dartコーディングスタイルガイド
- flutterの最もしっくりくるState管理法 - Provider + ChangeNotifierの使い方 | Tesshu's Blog
- Flutterでproviderを使ったMVVM開発|yasukotelin|note
- Flutterのpackage:providerを使ったBloc的アーキテクチャ全体像をサンプルで理解するまとめ | Qiita
- Flutter package:provider の各プロバイダの詳細 | Qiita
- Provider のススメ | Unselfish Meme
Discussion