【超初心者向け】FlutterのStatefulWidgetとStateについて
こんにちは、ワニかず@40歳 出戻りエンジニアです。
今回は、Flutterにおける状態管理の基本的な仕組みである
StatefulWidgetとStateの関係についてまとめてみました。
StatefulWidgetとStateの基本構造
Flutterでは、状態(state)を持つウィジェットを作成する場合、2つのクラスを定義します:
-
StatefulWidget
を継承したクラス:このクラスはウィジェットの設定(configuration)を定義しますが、実際の状態は持ちません。 -
State
を継承したクラス:このクラスが実際の状態を保持し、ウィジェットの描画方法を定義します。
これら2つのクラスは、常にペアで動作します。StatefulWidget
クラスの createState()
メソッドが対応する State
クラスのインスタンスを生成します。
なぜ2つのクラスが必要なのか?
Flutterがこのような設計を採用している理由は、ウィジェットの不変性(immutability)と状態の永続性(persistence)を両立させるためです。
Flutterでは、ウィジェットは不変(immutable)であるべきという設計思想があります。つまり、一度生成されたウィジェットのプロパティは変更できません。しかし、アプリは状態を変更する必要があります。
この矛盾を解決するために、Flutterは状態をウィジェットとは別のクラス(State)に保持させる設計を採用しています。ウィジェットが再構築されても、対応するStateオブジェクトは維持されるため、状態が失われることはありません。
setState メソッドの役割
setState
メソッドは、この State
クラスのメソッドです。つまり、setState
は直接 StatefulWidget
のメソッドではなく、それに対応する State
クラスが提供するメソッドなのです。
setState
の役割は2つあります:
- 状態の更新:
setState
に渡されたコールバック関数内で状態を更新します。 - 再描画のトリガー:状態が更新されたことをFlutterのフレームワークに通知し、ウィジェットの再描画をトリガーします。
実践的な例:カウンターアプリ
具体例として、シンプルなカウンターアプリを通じて、StatefulWidgetとStateの関係を見てみましょう:
// StatefulWidget を継承したクラス
class Counter extends StatefulWidget {
const Counter({Key? key}) : super(key: key);
_CounterState createState() => _CounterState(); // State クラスを生成
}
// State クラスを継承したクラス
class _CounterState extends State<Counter> {
int _count = 0; // 状態(state)
void _increment() {
setState(() { // setState は State クラスのメソッド
_count++;
});
}
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'カウント: $_count',
style: TextStyle(fontSize: 24),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _increment,
child: Text('カウントアップ'),
),
],
);
}
}
このコードでは:
-
Counter
クラス(StatefulWidget)は設定を定義し、createState()
メソッドで対応する状態クラスを生成します。 -
_CounterState
クラス(State)は実際のカウント値(_count
)を状態として保持し、ウィジェットの描画方法(build
メソッド)を定義します。 - ボタンが押されると
_increment
メソッドが呼び出され、その中でsetState
を使用してカウントを増やし、UIの再描画をトリガーします。
StatefulWidgetとStateのライフサイクル
StatefulWidgetとStateは、以下のようなライフサイクルを持ちます:
- StatefulWidgetの生成: ウィジェットツリーにStatefulWidgetが追加されると、そのコンストラクタが呼び出されます。
-
createState()の呼び出し: StatefulWidgetの
createState()
メソッドが呼び出され、対応するStateオブジェクトが生成されます。 -
initState(): Stateが初期化され、
initState()
メソッドが呼び出されます。このメソッドは、Stateが作成された直後に一度だけ呼び出されます。 - didChangeDependencies(): Stateが依存するオブジェクトが変更されたときに呼び出されます。
- build(): UIを構築するためのメソッドです。状態が変更されるたびに呼び出されます。
- didUpdateWidget(): ウィジェットが更新されたときに呼び出されます。
- setState(): 状態を更新し、Flutterに再描画を通知します。
- deactivate(): Stateがウィジェットツリーから一時的に削除されるときに呼び出されます。
- dispose(): Stateが完全に破棄されるときに呼び出されます。リソースの解放などのクリーンアップ処理を行います。
効果的な状態管理のためのベストプラクティス
-
最小限の状態更新:
setState
内では、必要な状態だけを更新しましょう。不必要な再描画を避けることでパフォーマンスが向上します。
// Good
setState(() {
_count++;
});
// Bad - 状態は変更しているが再描画が不要な処理も含めている
setState(() {
_count++;
print('Count updated to $_count'); // 状態更新と無関係な処理
});
-
状態の局所化: 状態はできるだけ使用される場所に近いウィジェットで管理しましょう。アプリ全体で使用される状態だけをグローバルに管理します。
-
コンテキストへのアクセス: State クラス内では
widget
プロパティを通じて対応する StatefulWidget インスタンスにアクセスできます。
class CounterWidget extends StatefulWidget {
final String title;
const CounterWidget({Key? key, required this.title}) : super(key: key);
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
Widget build(BuildContext context) {
// widget.title で StatefulWidget のプロパティにアクセス
return Text('${widget.title}: $_count');
}
}
まとめ
Flutterにおける状態管理の基本は、StatefulWidgetとStateの関係を理解することです。StatefulWidgetがウィジェットの設定を定義し、Stateがその状態と描画方法を管理するという分離された役割は、Flutterの宣言的UIパラダイムにおいて重要な設計パターンです。
setState
メソッドは StatefulWidget
自体ではなく、それに対応する State
クラスのメソッドとして使用されるという点を理解することで、Flutterにおける状態管理の仕組みをより深く理解できるでしょう。
より複雑なアプリケーションでは、Provider、Riverpod、BlocパターンといったFlutterの状態管理ソリューションも検討することをお勧めします。しかし、いずれの手法も根底にはここで説明したStatefulWidgetとStateの基本的な関係があることを忘れないでください。
Discussion