StatefulWidgetとはなんでしょうか?
状態を持つWidgetという認識で間違いありませんが、少し大雑把すぎます。私もFlutterを勉強する中でStatefulWidgetの理解に時間をかけてしまっていたので、私自身も含めて初学者向けにStatefulWidgetについて認識を共有しようと思います。
カウンターアプリを例に解説します。
class ProductCard extends StatefulWidget {
// 外部から渡される値(設定)を定義
final String productName;
final int price;
final Color backgroundColor;
// コンストラクタで値を受け取る
const ProductCard({
super.key,
required this.productName,
required this.price,
this.backgroundColor = Colors.white,
});
// Stateクラスを作成(1回だけ実行)
@override
State<ProductCard> createState() => _ProductCardState();
}
class _ProductCardState extends State<ProductCard> {
// 変更される値(状態)を定義
bool isFavorite = false;
@override
Widget build(BuildContext context) {
// widget.を使ってWidgetクラスの値を参照
return Card(
color: widget.backgroundColor,
child: Column(
children: [
Text(widget.productName),
Text('${widget.price}円'),
// 状態に基づいて表示を変更
IconButton(
icon: Icon(
isFavorite ? Icons.favorite : Icons.favorite_border
),
onPressed: () {
setState(() {
isFavorite = !isFavorite; // 状態を更新
});
},
),
],
),
);
}
}
StatefulWidgetには2つのクラスが存在します。
StatefulWidgetクラス
- 外部から設定を受け取る
- 例ではproductName、price、backgrounfColorがこれに当たります
- コンストラクタで値を受け取り、Stateクラス内に適用します
- createStateを実行しStateクラスを作成
- 1度だけ実行し、Stateを作成
Stateクラス
- 状態を持つ値を管理
- 例ではisFavoriteがこれに当たります
- build()で画面を構築
- setStateを実行するとbuild()が再実行され、UIが更新される
- setStateがなくても値は変更しますが、画面には反映されません
注意点
状態を持つ値、すなわち変数の定義位置に注意してください。
class _ProductCardState extends State<ProductCard> {
// 本来の状態定義位置
@override
Widget build(BuildContext context) {
// ここで状態を定義してはいけない
return Card(
build()内で変数を定義すると、setStateが実行されるたびに変数が初期値になってしまいます。
例ではボタンをタップするとisFavoriteが反転(true↔︎false)しますが、定義位置がbuild内にある場合UIが変更されません。
※具体的にはタップした時点では値は変更されていますが(setState実行前)、setStateが実行されると初期値に戻るため結果的にUIに変更が生じません。
まとめ
- 変更可能な状態(データ)を持つことができる
- 状態の変更には必ずsetState()を使う
- UIの更新は自動的に行われる
- StatefulWidgetとStateクラスの2つで1セット
発展的な使い方
- 複数の状態を持つWidget
- 親Widgetからのデータ受け渡し
- アニメーションの制御
- フォームの状態管理