StateはFlutterにおける中央データ管理オブジェクトであり、StatefulWidgetに関連付けられ、変更可能な情報の保存とインターフェースの構築を担当します。公式Flutterドキュメント(Flutter.dev, 2026)によると、Stateはウィジェットのライフサイクル全体を通じて存在し、その再構築後も存続して、UI更新間のデータ一貫性を保証します。ウィジェット自体とは異なり、Stateはそのフィールドを変更し、setState呼び出しを通じて再構築を開始できます。
重要ポイント
StateはFlutterアーキテクチャのオブジェクトで、StatefulWidgetの変更可能なデータを保存し、このデータがインターフェースにどのように表示されるかを決定します。各StatefulWidgetは、ツリーに挿入されるときに、createStateメソッドを介して正確に1つのStateオブジェクトを作成します。Stateはウィジェットから独立して存在します。親が新しいパラメータでStatefulWidgetを再構築しても、Stateは同じままで、widgetプロパティを介して更新されたウィジェットを受け取ります。
Flutterアーキテクチャ概要(Google, 2026)によると、WidgetとStateの分離は、フレームワークがツリー要素を再利用できるようにする意図的なアーキテクチャ上の決定です。ウィジェット(軽量な記述)は複数回作成および破棄できますが、State(データを持つ重いオブジェクト)は要素がツリーにある限りメモリに残ります。これにより、親ウィジェットの頻繁な再構築中のデータ損失を防ぎます。
Stateはジェネリックを介してStatefulWidgetインターフェースを実装します:class _MyState extends State<MyWidget>。ジェネリックはStateを特定のStatefulWidgetタイプにバインドし、widgetプロパティを介してそのフィールドへの型安全なアクセスを提供します。
StateオブジェクトはStatefulElementに保存されます — WidgetとRenderObjectの中間層です。StatefulElementはcreateStateを介してStateを作成し、その参照を保持し、Stateを所有者として渡します。Elementはウィジェットがツリーから削除されたときにのみ破棄されます — それまではStateはメモリに残ります。
Stateのライフサイクルは決定論的で、厳密な呼び出し順序で構成されています。この順序を理解することは、正しいリソース管理とメモリリーク防止の基盤です。
initStateはState作成時に最初に呼び出されます。このメソッドでは、コントローラー、ストリーム購読、タイマー、フィールドの初期値が初期化されます。最初の行でsuper.initState()を呼び出すことが必須です。initState段階では、ウィジェットツリーはまだ完全にマウントされていないため、MediaQuery.of(context)などのメソッドが正しく動作しない場合があります。
didChangeDependenciesはinitStateの後、およびInheritedWidgetの依存関係が変更されるたびに呼び出されます。MediaQuery.of(context)やTheme.of(context)はinitStateではなく、ここで呼び出すべきです。この時点までにツリーは既にマウントされているからです。このメソッドは、ウィジェットがInheritedWidgetが異なる値を提供する別のコンテキストに移動した場合にも呼び出されます。
buildはStateの主要メソッドで、ウィジェットツリーを返します。initStateの後、didChangeDependenciesの後、および各setStateの後に呼び出されます。buildメソッドには副作用があってはなりません — 現在のStateフィールド値に基づいてインターフェースを記述するのみです。
didUpdateWidgetは、親が新しいパラメータでStatefulWidgetを再構築するときに呼び出されます。StateはoldWidgetを介して古いウィジェットにアクセスし、新しいものと比較できます。パラメータが変更された場合、状態を更新したり、新しいデータをロードしたり、アニメーションを再開したりできます。
disposeはすべてのリソース(コントローラー、購読、タイマー)が解放される最終メソッドです。dispose後、Stateは死亡としてマークされます:mountedはfalseを返し、setStateを呼び出すと例外がスローされます。メソッドの最後の行でsuper.dispose()を呼び出すことが必須です。
| メソッド | 呼び出しタイミング | 必須のsuper |
|---|---|---|
| initState | State作成時 | はい、最初の行 |
| didChangeDependencies | initState後およびInheritedWidget変更時 | はい |
| build | initState、didChangeDependencies、setState後 | いいえ |
| didUpdateWidget | 親から新しいウィジェットを受信時 | はい |
| setState | 開発者の呼び出し時 | いいえ |
| dispose | ツリーから削除時 | はい、最後の行 |
Stateの動作メカニズムは、Elementとの関連付け、setStateによるリアクティビティ、widgetプロパティによる親へのアクセスの3つの主要原則に基づいています。Flutterが要素ツリーを構築し、StatefulElementに遭遇すると、関連付けられたウィジェットのcreateStateを呼び出します。作成されたStateは要素に保存され、要素が削除されるまで存在します。
setStateが呼び出されると、Stateは自身をダーティとしてマークし、次のフレームの再構築をスケジュールします。重要な点:setStateはbuildを即座に呼び出しません — 再構築の必要性を登録するだけです。Flutterは現在のフレームのすべてのダーティ要素を収集し、それらを一括で再構築してパフォーマンスを最適化します。build呼び出し後、Stateはクリーン状態に戻ります。
widgetプロパティにより、StateはStatefulWidgetコンストラクタに渡されたパラメータを読み取ることができます。StatefulWidgetは不変(StatelessWidgetと同様)であるため、そのフィールドは変更されません — パラメータが変更されると、親が新しいウィジェットを作成し、StateはdidUpdateWidgetを介してそれを受け取ります。これにより、Stateが常に最新の親データで動作することが保証されます。
タイマーで変更されるフィールドを持つ基本的なStateの例。initState、setState、disposeを示します:
class _TimerWidgetState extends State<TimerWidget> {
int _seconds = 0;
Timer? _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(
const Duration(seconds: 1),
(_) => setState(() => _seconds++),
);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text('$_seconds seconds elapsed');
}
}
widgetプロパティを使用して親パラメータにアクセスし、didUpdateWidgetを介してその変更に反応する例:
class _GreetingState extends State<GreetingWidget> {
String _displayName = '';
@override
void initState() {
super.initState();
_displayName = _formatName(widget.name);
}
@override
void didUpdateWidget(GreetingWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.name != oldWidget.name) {
setState(() {
_displayName = _formatName(widget.name);
});
}
}
String _formatName(String name) => name.trim().isEmpty ? 'Guest' : name;
@override
Widget build(BuildContext context) {
return Text('Hello, $_displayName!');
}
}
2番目の例では、Stateが入力パラメータnameの変更を追跡し、実際の変更があった場合にのみ表示を再フォーマットします。widget.name != oldWidget.nameのチェックがないと、名前が変更されていなくても、親が再構築されるたびにメソッドが呼び出され、フレームワークにとって不必要な作業が発生します。
StateとStatefulWidgetは、Flutterアーキテクチャにおいて異なる役割を果たす2つの異なるクラスです。StatefulWidgetは、ウィジェット設定を記述しStateを作成する軽量な不変のラッパーです。Stateは、変更可能なデータを保存し、購読を管理し、UIを構築する重いオブジェクトです。この分離により、Flutterは状態を失うことなくウィジェットを破棄および作成できます。
StatefulWidgetのすべてのフィールドはfinalでなければならず、コンストラクタで設定されます — 作成後は変更されません。一方、Stateはいつでもそのフィールドを変更できますが、すべての変更の前にsetState呼び出しが必要であり、Flutterが再構築の必要性を認識できるようにします。これが主な違いです:StatefulWidgetは“何を表示するか”、Stateは“どのように表示し、どのデータを使用するか”です。
Flutterソースコード分析(Flutter SDK, 2026)によると、StatefulWidgetには必須フィールドが1つだけ含まれています — createState、一方StateはBuildContextにアクセスでき、ストリームを購読し、アニメーションやコントローラーを管理できます。StatefulWidgetは可能な限りシンプルに保ち、すべてのロジックをStateに移すことが推奨されます。
WidgetとStateの分離は、設定の不変性を保証するアーキテクチャ上の決定です。StatefulWidget自体が状態を保存した場合、親が再構築されるたびに状態が失われます。状態を別のオブジェクトに移動することで、Flutterはデータが再構築後も存続することを保証し、ウィジェットは軽量で比較可能なまま維持されます。
Stateオブジェクトは分離されています — 他のウィジェットのStateに直接アクセスすることはできません。ウィジェット間のデータ交換には、InheritedWidgetまたは外部の状態管理ツール(Provider、Riverpod、Bloc、Redux)が使用されます。各アプローチは問題を異なる方法で解決します:InheritedWidgetはウィジェットツリーを介して動作し、ProviderはDIコンテナを介して、Blocはイベントストリームを介して動作します。
ツールの選択はプロジェクトの規模によって異なります。小さなアプリケーションには、InheritedWidgetとローカルStateで十分です。中規模から大規模なプロジェクトには、RiverpodまたはBlocが推奨されます — これらはテスト容易性、予測可能性、UIからのロジックの分離を保証します。Stateはローカルウィジェットデータ(フォーカス、スクロール、アニメーション)にのみ使用されます。
Flutterコミュニティ調査2025(Flutter Foundation、2025年12月)によると、Riverpodは新規プロジェクトで最も人気のある状態管理ソリューション(38%)であり、Bloc(31%)、Provider(22%)がそれに続きます。3つのツールすべてがStateと互換性があり、標準のライフサイクルを放棄する必要はありません。
最初の間違いは、非同期コールバックでsetStateの前にmountedをチェックするのを忘れることです。ウィジェットがツリーから削除された(ユーザーが画面から移動したなど)が、非同期操作(HTTPリクエスト)がまだ実行中の場合、その完了後にStateは既に死亡しています。死亡したStateでsetStateを呼び出すと例外がスローされます。if (mounted) setState(...)のチェックで問題を解決します。
2番目の間違いは、didChangeDependenciesの代わりにinitStateでInheritedWidget依存関係を初期化することです。initStateではコンテキストがまだマウントされていないため、MediaQuery.of(context)は例外をスローします。すべてのInheritedWidget依存関係はdidChangeDependenciesまたはbuildで設定する必要があります。
3番目の間違いは、setStateを呼び出さずにフィールドを変更することです。開発者がsetStateなしでStateフィールドを変更した場合、Flutterは変更を認識できず、UIは更新されません。例:後続のsetState((){})なしの_list.add(item)はリストを変更しますが、画面は同じままです。
Stateでの非同期操作のための安全パターン:
Future<void> _fetchData() async {
final data = await ApiService.fetch();
if (mounted) {
setState(() => _data = data);
}
}
mountedのチェックにより、setStateが生存しているStateでのみ呼び出され、“setState called after dispose”例外を防ぎます。
よくある質問
StatefulWidgetは不変のウィジェット設定であり、Stateはデータを保存しライフサイクルを管理する可変オブジェクトです。ウィジェットは再作成できますが、Stateはできません。StatefulWidgetはcreateStateを介してStateを作成します。
正確に1つです。createStateメソッドは、StatefulWidgetが最初にツリーに挿入されるときに1回呼び出されます。親が複数回再構築しても、ウィジェットのタイプまたはKeyが変更されない限り、Stateオブジェクトは同じままです。
mountedは、Stateがウィジェットツリーにあるかどうかを示すブールフラグです。disposeを呼び出した後、mountedはfalseになります。例外を避けるために、非同期コールバックでsetStateの前のチェックに使用されます。
いいえ。Stateは常にジェネリックを介して特定のStatefulWidgetにバインドされています:State<T extends StatefulWidget>。ウィジェットとの関連付けなしに直接Stateを作成することは、アーキテクチャ上不可能です。
例外がスローされます:“setState called after dispose”。dispose後、Stateは死亡とみなされ、setStateを介してUIを再構築しようとする試みはすべて禁止されます。解決策は、各setStateの前にmountedをチェックすることです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。