StatefulWidgetは、可変状態を持つFlutterウィジェットで、UIがユーザーアクション、非同期イベント、データストリームに反応できるようにします。公式Flutterドキュメント(Flutter.dev、2026)によると、StatefulWidgetはアプリケーションのすべてのインタラクティブ要素(入力フォーム、アニメーション、チェックボックス、スイッチ、ネットワークからデータをロードする画面)に使用されます。StatelessWidgetとは異なり、StatefulWidgetはライフサイクル全体を通じて永続化する個別のStateオブジェクトを作成し、ウィジェット自体を再作成せずに再構築できます。
重要なポイント
StatefulWidgetは、ユーザーアクション、システムイベント、非同期操作に応じて状態を変更できるFlutterクラスです。StatelessWidgetとは異なり、StatefulWidgetは直接レンダリングされず、レンダリングを担当するStateオブジェクトを作成します。WidgetとStateの2つのクラスに分離することで、Flutterはウィジェット自体を再作成せずにUIを再構築でき、頻繁な更新時に大幅なパフォーマンス上の利点を提供します。
StatefulWidgetのアーキテクチャは“可変と不変の分離”パターンに従います。ウィジェット自体は(StatelessWidgetのように)不変のままですが、すべての可変状態は個別のStateオブジェクトに保存されます。これにより、FlutterはタイプとKeyで比較してウィジェットを再利用でき、再構築間で実際の状態を保持できます。
Google(Flutter Architectural Overview、2026)によると、StatefulWidgetはウィジェットの存続期間中に状態が複数回変化するシナリオ(テキストフィールド、アニメーション、タイマー、データストリーム、非同期ロード)に最適です。1回限りの初期化にはStatelessWidgetで十分です。
StatefulWidgetは、ウィジェットが外部イベント(ボタンクリック、HTTPリクエストの完了、データベース更新、WebSocket購読)に応答する必要がある場合に必須です。また、アニメーション付きウィジェット、コントローラー付きテキストフィールド、フォーカスを管理するコンポーネントにも必要です。ウィジェットが単にデータを表示するだけでイベントを生成しない場合は、StatelessWidgetを使用してください。
StatefulWidgetは2つのクラスで構成されています。StatefulWidget自体(軽量、不変)とState(重量、可変)です。フレームワークは、ツリーに挿入される際に1回呼び出されるcreateState()メソッドを介してStateを作成します。Stateはwidgetプロパティを介してウィジェットへの参照を取得し、ライフサイクルの任意の時点でそのフィールドにアクセスできます。
ライフサイクルは6つの主要な段階で構成され、各段階は特定のタスクを実行するためのオーバーライド可能なメソッドを提供します。これらの段階を理解することは、適切なリソース管理とメモリリークの回避に不可欠です。
createStateは、StatefulWidgetがツリーに挿入される際に呼び出される最初のライフサイクルメソッドです。このウィジェットに関連付けられた新しいStateインスタンスを返す必要があります。このメソッドは要素の全生涯で正確に1回呼び出されます。ここで重い操作を行わないことが重要です — createStateは可能な限り軽量であるべきです。
initStateはStateの作成直後、最初のUI構築の前に呼び出されます。ここではコントローラー(TextEditingController、AnimationController)の初期化、データストリーム(StreamSubscription)の購読、タイマーのセットアップ、フィールドの初期化を行います。Flutterドキュメント(Flutter.dev、2026)によると、initStateではBuildContext.of()を呼び出せません — ツリーがまだ完全にマウントされていません。
didChangeDependenciesはinitStateの後、およびInheritedWidgetの依存関係が変更されるたびに呼び出されます。これはMediaQuery.of(context)の呼び出しやThemeの購読に適した場所です — アプリケーションの実行中に変更される可能性のある値です。ウィジェットがInheritedWidgetを使用する場合、初期化ロジックはinitStateではなくここに配置する必要があります。
buildはウィジェットツリーを返す主要なメソッドです。initStateの後、didChangeDependenciesの後、および各setStateの後に呼び出されます。didUpdateWidgetは親が再構築され、新しいパラメーターを持つStatefulWidgetを渡す際に呼び出されます。ここで古いウィジェットフィールドと新しいものを比較し、必要に応じて状態を更新できます。
disposeはライフサイクルの最終段階です。ここですべてのリソースが解放されます。ストリームの購読解除、コントローラーの破棄、タイマーのキャンセルなどを行います。disposeを呼び出さないとメモリリークが発生します。dispose後、Stateは死亡したと見なされ、その内部でsetStateを呼び出すと例外がスローされます。
StatefulWidgetの動作メカニズムは、Widget(軽量な記述)、Element(中間層)、State(データストレージ)の3つのエンティティの協調動作に基づいています。Flutterが記述内でStatefulWidgetを検出すると、StatefulElementを作成し、createStateを呼び出してStateオブジェクトへの参照を保存します。親が再構築されると、Flutterは新しいウィジェットを現在のElementと比較します — タイプとKeyが一致する場合、Elementは更新され、Stateは同じままです。
状態はsetState呼び出しを介してのみ変更され、フレームワークに再構築の必要性を通知します。重要なのは、setStateは自動的に状態を変更せず、ウィジェットを“ダーティ”としてマークするだけです。開発者はsetStateに渡されたコールバック内でStateフィールドを独立して更新します。コールバック完了後、Flutterはbuildを呼び出してUIを更新します。
Dart/Flutterチーム(Dart Language Specification、2026)によると、この分離により、buildが呼び出される前にすべての状態変更が同期的に発生し、UIが部分的に更新されたデータを表示する状況を排除します。これはFlutterにおけるインターフェース一貫性の重要なメカニズムです。
簡単なStatefulWidget — ボタンクリックカウンターを見てみましょう。基本的なパターン(Stateの作成、initStateでのフィールド初期化、setStateによる変更)を示しています。
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}
非同期データ読み込みとライフサイクル管理の例。StatefulWidgetがネットワークからデータを読み込み、読み込み状態を表示します。
class UserProfilePage extends StatefulWidget {
final String userId;
const UserProfilePage({super.key, required this.userId});
@override
State<UserProfilePage> createState() => _UserProfilePageState();
}
class _UserProfilePageState extends State<UserProfilePage> {
UserModel? _user;
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
final user = await UserService.fetchUser(widget.userId);
setState(() {
_user = user;
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
if (_isLoading) return const CircularProgressIndicator();
return Text('Hello, ${_user!.name}');
}
}
2つ目の例で重要な点:initStateは非同期操作を開始しますが、メソッド自体は非同期ではありません。非同期性は別のメソッド_loadUser内でasync/awaitを介して実装され、リクエスト完了後にsetStateを介して状態を更新します。このアプローチにより、データを受信する前にウィジェットがローディングインジケーターを正しく表示できます。
StatefulWidgetとStatelessWidgetの選択は、単に状態を持つかどうかだけの問題ではありません。StatefulWidgetはinitState、didChangeDependencies、didUpdateWidget、disposeメソッドを持つ完全なライフサイクルを提供し、コントローラー、アニメーション、ストリームの操作に必要です。一方、StatelessWidgetにはこれらのメソッドがなく、フレームワークにとって常に軽量です。
Flutterチームの推奨(Flutter docs、2026)は、状態をツリーの上位に移動(State Hoisting)するか、状態管理ソリューション(Riverpod、Bloc、Provider)を使用して、アプリケーション内のStatefulWidgetの数を最小限に抑えることです。各StatefulWidgetは要素が削除されるまで存続するStateオブジェクトを作成します — このようなウィジェットが多いほど、メモリ負荷が高くなります。
| 基準 | StatefulWidget | StatelessWidget |
|---|---|---|
| 状態 | 可変 | 不変 |
| ライフサイクル | 6段階 | buildのみ |
| Stateオブジェクト | 別途作成 | 不要 |
| setState | 利用可能 | 利用不可 |
| 購読 | initState/dispose | 非対応 |
| constコンストラクタ | 制限あり | 完全対応 |
| メモリ消費 | 高い | 低い |
StatefulWidgetはStateオブジェクトを作成・維持する必要があるため、StatelessWidgetよりも多くのリソースを必要とします。ただし、いくつかのルールに従えば、StatefulWidgetの適切な使用はパフォーマンス問題を引き起こしません。まず、StatefulWidgetの深いネストを避けてください — 各レベルがツリー走査にオーバーヘッドを追加します。次に、複雑なStatefulWidgetを複数の単純なウィジェットに分割し、それぞれが状態の自分の部分を担当するようにします。
Flutterパフォーマンス調査(Flutter.dev、2026年2月)によると、FPS低下の最も一般的な原因は、表示が変更されていないStatelessWidgetを含むすべての子孫を再構築する親ウィジェットでのsetState呼び出しです。解決策は、UIの可変部分を個別のStatefulWidgetに抽出し、setStateが必要最小限のウィジェットのみを再構築するようにすることです。
State内でconstを使用することも重要なテクニックです。子ウィジェットがconstとして宣言されている場合、親でsetStateが呼び出されてもFlutterはそれらを再構築しません。これによりフレームワークの負荷が軽減され、フレームレンダリング時間が短縮されます。
各setState呼び出しは完全なウィジェット再構築をトリガーします。状態が高頻度で変化する場合(アニメーションやデータストリームなど)、手動でsetStateを呼び出す代わりにAnimatedBuilder、ValueListenableBuilder、またはStreamBuilderの使用を検討してください。これらのウィジェットは再構築を最適化し、実際に変更されたUIの部分のみを更新します。
StatefulWidgetに関する最初のよくある間違いは、dispose後にsetStateを呼び出すことです。ウィジェットがツリーから削除されると、Stateは死亡したと見なされ、setStateを呼び出すと“setState called after dispose”例外がスローされます。これは、ウィジェットが削除された後に非同期操作が完了した場合に最も頻繁に発生します。解決策は、setStateを呼び出す前にmountedフラグを確認するか、disposeで非同期操作をキャンセルすることです。
2つ目の間違いは、buildメソッドで重い計算を実行することです。buildはすべてのsetStateおよびすべての親の再構築時に呼び出されるため、すべての計算は可能な限り軽量であるべきです。リソース集約型の操作が必要な場合は、別のIsolateに移動するか、結果をStateフィールドにキャッシュしてください。
3つ目の間違いは、super.initState()とsuper.dispose()を呼び出さないことです。これらのメソッドをオーバーライドする場合、開発者は親の実装を呼び出す必要があります。そうしないと、フレームワークがElementの状態を適切に管理できず、追跡が困難なバグが発生します。
mountedを確認するsuper.initState()とsuper.dispose()を呼び出すことを忘れないよくある質問
StatefulWidgetはsetStateを介して状態を変更でき、ライフサイクル(initState、dispose)を持ち、個別のStateオブジェクトを作成します。StatelessWidgetは状態を変更できず、ライフサイクルメソッドもありません — 単に渡されたデータを表示するだけです。
createStateは各StatefulElementインスタンスに対して正確に1回呼び出されます。親が複数回再構築されても、ウィジェットのタイプとKeyが変更されない限り、createStateは呼び出されず、既存のStateオブジェクトが使用されます。
リソースが解放されません。コントローラーはバックグラウンドで動作し続け、ストリーム購読はアクティブなまま、タイマーはキャンセルされません。これによりメモリリークが発生し、dispose後のsetState呼び出しが例外をスローする可能性があります。
はい、StatefulWidgetのコンストラクタはconstにできます。ただし、StatelessWidgetほどメリットはありません — Stateオブジェクトは最初の挿入時に依然として作成されます。constはウィジェット自体(軽量ラッパー)にのみ影響し、Stateには影響しません。
didUpdateWidgetは親が新しいパラメーターでStatefulWidgetを渡す際に呼び出されます。これは状態を新しいデータと同期させるために必要です — 例えば、パラメーターのuserIdが変更された場合、新しいユーザーのプロファイルをロードする必要があります。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。