【Flutter】状態管理①:StatefulWidget + setStateからproviderへ。カウンターアプリを例に
Flutterにおける状態管理の進化
Flutterでは、**StatefulWidget
**を使用して内部状態を持つことができる。また、ProviderやRiverpod、Blocなどのライブラリを使用してアプリケーション全体の状態管理を行う。
本記事では、statefulWidget + setStateから、次のステップであるProviderまでをみていく。別記事でRiverpodについて触れる予定。
StatefulWidget + setState
StatefulWidget
を使用すると、StatelessWidget
と異なり、ウィジェットがアプリケーションの実行中に変更することができる内部状態を持つことができる。そして、setState
メソッドは、この内部状態が変更された際にウィジェットを再構築(再描画)するために使用される。
カウンターアプリなど、ユーザーのアクションに応じて状態が変化する画面を作成したい場合にFlutterを始めた初心者の段階で、まず勉強する組み合わせである。
例)カウンターアプリ
ユーザーがボタンを押すと、カウンターが増加し、画面上のカウントが更新される。
import 'package:flutter/material.dart';
class Counter extends StatefulWidget {
// このクラスは状態の設定です。
// 親によって提供され、Stateのbuildメソッドで使われる値を保持する。
// Widgetサブクラスのフィールドは常に"final"としてマークされる。
const Counter({super.key});
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
// setStateの呼び出し: setStateが呼び出されると、
// Flutterは該当のStateオブジェクトが保持する
// buildメソッドを再実行するようスケジュールする。
setState(() {
_counter++;
});
}
Widget build(BuildContext context) {
// このメソッドはsetStateが呼ばれるたびに再実行される。
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
const SizedBox(width: 16),
Text('Count: $_counter'),
],
);
}
}
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: Counter(),
),
),
),
);
}
**_increment
が呼ばれると、_counter
の値が増加し、setState
によってbuild
メソッドが再実行され、Text
**ウィジェットに表示されるカウントの値が更新される。
MaterialApp
|
Scaffold
|
Center
|
Counter (StatefulWidget)
|
_CounterState (State)
|
Row
/ \
ElevatedButton Text
(Increment) (Count: x)
Flutterの**build
メソッドは、setState
が呼ばれるたびに再実行され、その結果としてWidget全体が再構築される。build
**メソッド内で新しいウィジェットツリーが生成されるが、実際には、Flutterフレームワークは差分(diffing)アルゴリズムを使用して、前回のビルドと比較し、実際に必要な変更のみをUIに適用する。
この例では、**_counter
変数が更新されると、この変数を含むText
ウィジェットのみが再描画される。しかし、ElevatedButton
**やその他のウィジェットは再構築の必要がないため、変更されない。
このプロセスは、Reactの仮想DOMと類似しており、不要なUIの再描画を最小限に抑えるように設計されている。
setStateの2つの課題
離れたWidget間で状態を共有するのが困難
**setState
は基本的にローカル状態の管理に適している。複数のウィジェット間で状態を共有したい場合や、アプリケーションの異なる部分から状態にアクセスしたい場合、setState
**だけでは状態の伝播やアクセスが難しくなる。
ビジネスロジックとUIの分離が困難→メンテナンスしにくい
**setState
**を使うと、ビジネスロジックとUIロジックが密接に結びついてしまいがち。状態管理をWidgetの外部に分離することで、コードの再利用性やテストの容易さが向上する。
というわけで、上記setStateの課題を解決すべく、Providerという概念が誕生した。
Provider
を導入して、カウンターアプリを実装
providerパッケージを導入
カウント状態を保持するCounterModelクラスを作成
状態を保持するクラスで、**ChangeNotifier
をMixinしている。ChangeNotifier
**は、状態が変更されたことをリスナーに通知する機能を提供する。
notifyListeners()
は、ChangeNotifier
mixinに定義されているメソッド。**notifyListeners()
が呼び出されると、そのオブジェクト(CountModel
クラスから生成されるインスタンスを指しており、この場合はChangeNotifierProvider
でcreate
関数を通じて生成されるCountModel
**のインスタンス)をリッスンしているすべてのウィジェットが再構築され、これにより、状態の変更がUIに反映されるようになる。
// lib/count_model.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; //Providerをimport
class CountModel with ChangeNotifier {
// 別ファイルから呼び出せるように_(プライベート化)を取って記載
int counter = 0;
void incrementCounter() {
counter++;
notifyListeners();
}
}
ChangeNotifierProviderで囲んでmodelをinitialize
**ChangeNotifierProvider
はCountModel
**のインスタンスを提供し、そのインスタンスに変更があった場合にリ、、、
続きはこちらで記載しています。
Discussion