導入
こんにちは、もんすんです。
roadmap.shのFlutterの「StateManagement」の章です。
状態管理に関する内容の学習になります。
日本では、かなり主力になっているRiverpod
に関しては、今回の記事では割愛します。
代わりにChangeNotifier
とValueNotifier
をメインで進めていきます。
状態管理
Flutterにおける状態管理とは、Flutterアプリケーションのデータや状態を管理・更新するプロセスを指します。
Flutterでは、ユーザーがアプリケーションとやり取りをする際などに、ウィジェットの状態が動的に変わることがあります。
F状態管理の手法には次のようなものがあります。
- ScopedModel: 状態を集中管理するためのモデルを使用するサードパーティ製の状態管理ソリューションです。
- Provider: 最小限のボイラープレートコードでウィジェットが状態にアクセスできるようにする軽量なソリューションです。
- BLoC (Business Logic Component): ストリームとリアクティブプログラミングを用いて状態を管理する手法です。
- Redux: ReactのReduxライブラリに触発された状態管理ソリューションです。
- InheritedWidget: 状態をウィジェットツリーに渡すことができる組み込みのウィジェットです。
どの状態管理手法を選ぶかは、プロジェクトの複雑さや規模によって異なります。
小規模なプロジェクトでは、ProviderやInheritedWidgetで十分かもしれませんが、大規模なプロジェクトでは、ScopedModelやReduxのようなより強力なソリューションが必要になることがあります。
ChangeNotifierクラス
FlutterのChangeNotifierは、Flutterにおける状態管理の基本クラスです。
これを使用することで、開発者はデータの変更を監視し、リスナーに通知することができ、ユーザーインターフェイスを効率的に更新することが可能になります。
ChangeNotifierを拡張することで、開発者は特定の状態やデータモデルを表すカスタムクラスを作成できます。
また、ChangeNotifierで状態を管理し、UIを動的に更新することで、ユーザー体験が向上します。
これにより、状態管理が簡素化され、インタラクティブなFlutterアプリケーションの作成が可能になります。
参考
https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html
ChangeNotifierは、VoidCallbackを使って通知するための変更通知APIを提供するクラスで、拡張またはミックスインとして使用できます。
リスナーの追加はO(1)、リスナーの削除と通知の送信はO(N)です。
ここでNはリスナーの数です。
データモデルにChangeNotifierサブクラスを使用する
データ構造がChangeNotifierを拡張またはミックスインすることで、Listenableインターフェースを実装し、ListenableBuilderなどのListenablesの変更を監視するウィジェットで利用できるようになります。
以下は、ListenableBuilderを使って、カウントを含むTextウィジェットの再ビルドを制限するシンプルなカウンターを実装した例です。
現在のカウントはChangeNotifierサブクラスに格納され、値が変更されるとListenableBuilderの内容が再ビルドされます。
import 'package:flutter/material.dart';
void main() {
runApp(const ListenableBuilderExample());
}
class CounterModel with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count += 1;
notifyListeners();
}
}
class ListenableBuilderExample extends StatefulWidget {
const ListenableBuilderExample({super.key});
@override
State<ListenableBuilderExample> createState() =>
_ListenableBuilderExampleState();
}
class _ListenableBuilderExampleState extends State<ListenableBuilderExample> {
final CounterModel _counter = CounterModel();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('ListenableBuilder Example')),
body: CounterBody(counterNotifier: _counter),
floatingActionButton: FloatingActionButton(
onPressed: _counter.increment,
child: const Icon(Icons.add),
),
),
);
}
}
class CounterBody extends StatelessWidget {
const CounterBody({super.key, required this.counterNotifier});
final CounterModel counterNotifier;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Current counter value:'),
ListenableBuilder(
listenable: counterNotifier,
builder: (BuildContext context, Widget? child) {
return Text('${counterNotifier.count}');
},
),
],
),
);
}
}
この場合、ChangeNotifierのサブクラスはリストをカプセル化しており、リストにアイテムが追加されるたびにクライアントに通知します。
ValueNotifier
FlutterのValueNotifierも、Flutterでの状態管理によく使われます。
ValueNotifierは、ChangeNotifier
を拡張したクラスで、単一の値を効率的に管理し、その変更をリスナーに通知します。
ValueNotifierを使用することで、開発者はカウンターやユーザー入力などの特定のデータを簡単に追跡し、更新することができます。
これにより、状態管理が簡略化され、動的なFlutterインターフェースの実現が可能になります。
参考
ValueNotifierは、ValueListenableBuilder
を使用してそれをリッスンしているウィジェットだけを再ビルドするため、パフォーマンス効率が高いらしいです。
ValueNotifierの作成
ValueNotifier
は簡単に作成できます。新しいファイルvalue_notifiers.dart
を作成し、すべてのValueNotifier
を一つのファイルにまとめます。ValueNotifier
は、int
、String
、double
、boolean
型のデータを保持できますし、自分のデータ型(クラスを使って)も使用できます。
import 'package:flutter/material.dart';
ValueNotifier<int> buttonClickedTimes = ValueNotifier(0);
アプリのシナリオ: ボタンがクリックされた回数をカウントするアプリを作成しています。ValueNotifier
の使い方を理解するために、ボタンのための独立したStatelessウィジェットクラスを作成し、カウンターを表示するためにStatelessウィジェットを親ウィジェットとして使います。
ValueNotifierの値を変更する
ボタンウィジェット内で、ValueNotifier
の値をインクリメントします。
// value_notifiers.dartをインポートし忘れないでください。さもないとエラーが発生します。
GestureDetector(
onTap: () => buttonClickedTimes.value = buttonClickedTimes.value + 1,
child: Text("Button", style: Theme.of(context).textTheme.button),
);
ValueListenableBuilder
ValueListenableBuilder
は、ValueNotifier
にアクセスし、ValueNotifier
の値に依存するUIの部分を再ビルドするために使用されるウィジェットです。
親ウィジェット内で、Text
ウィジェットをValueListenableBuilder
で囲みます。
ValueListenableBuilder(
valueListenable: buttonClickedTimes,
builder: (BuildContext context, int counterValue, Widget child) {
return Text("Counter: $counterValue");
},
)
builder:(@required BuildContext context, @required int counterValue, Widget child)
は、ValueNotifier
の値が変更されるたびにUIを再ビルドします。
ネストされたValueListenableBuilder
ウィジェットが複数のValueNotifier
に依存している場合、ネストされたValueListenableBuilder
を使用します。
ValueListenableBuilder(
valueListenable: buttonClickedTimes,
builder: (BuildContext context, int counterValue, Widget child) {
return ValueListenableBuilder(
valueListenable: textColor,
builder: (context, textColorValue, child) {
return Text(
"Counter: $counterValue",
style: TextStyle(color: textColorValue),
);
},
);
},
)
ValueNotifiersの破棄
使用されなくなったValueNotifier
を破棄することは、メモリの損失を防ぐための良い習慣です。
@override
void dispose() {
buttonClickedTimes.dispose();
textColor.dispose();
super.dispose();
}
Optional Parameter Child
child
パラメータはオプションですが、ValueNotifier
とValueListenableBuilder
をさらにパフォーマンス効率よく使用するために役立ちます。
ValueListenableBuilder(
valueListenable: ColorNotifier,
builder: (BuildContext context, int colorValue, Widget child) {
return Container(
height: 100,
width: 100,
color: colorValue,
child: child,
);
},
child: Text("button", textAlign: TextAlign.center),
)
上記のコードでは、Container
はcolorNotifier
を使用して色を変更していますが、Text
ウィジェットはcolorNotifier
の値に関係なく同じ値を持っています。しかし、Container
の子であるため、Container
が再ビルドされるとText
ウィジェットも再ビルドされます。この問題に対処するために、オプションのchild
パラメータが役立ちます。
終わりに
今回は、Flutterにおける状態管理の基本概念と、ChangeNotifier
およびValueNotifier
の使用方法について詳しく解説しました。
状態管理は、アプリケーションの規模や要件に応じて適切な方法を選択することが重要です。
この記事を参考に、プロジェクトに最適な方法を見つけてみてください!