CONTENT
ここから
この章では、StatefulWidgetが画面の状態をどのように保持し、変更内容を画面へ反映するのかを整理します。
第8章ですでにModelViewerScreenをStatefulWidgetへ変更しましたが、現時点では3Dコントローラーを保持する目的が中心でした。今後は、モデルの読み込み中、表示完了、エラー発生といった状態も管理します。
10.1 StatelessWidgetとの違い
StatelessWidgetは、表示中に変化する値を内部へ保持しないWidgetです。タイトルや固定された説明文など、同じ内容を表示し続ける部品に適しています。
一方、StatefulWidgetは、操作や処理の結果によって画面が変わる場合に使用します。
今回のアプリでは、次のような変化を扱います。
- モデルの読み込みが完了する
- 読み込み中の案内を消す
- エラーメッセージを表示する
- 再読み込みによって状態を初期化する
- モデル表示後にリセットボタンを有効にする
これらは実行中に変化するため、StatefulWidgetが必要です。
10.2 StatefulWidgetとStateの関係
StatefulWidgetは、Widgetの設定を表します。実際に変化する値は、対応するStateクラスへ保持します。
class ModelViewerScreen extends StatefulWidget {
const ModelViewerScreen({super.key});
@override
State<ModelViewerScreen> createState() {
return _ModelViewerScreenState();
}
}
createState()は、Widgetに対応する状態オブジェクトを生成します。
class _ModelViewerScreenState extends State<ModelViewerScreen> {
final Flutter3DController _controller = Flutter3DController();
@override
Widget build(BuildContext context) {
// 画面を構築する
}
}
ModelViewerScreen自体の設定は変更せず、コントローラーや読み込み状態などは_ModelViewerScreenStateへ置きます。
10.3 状態を保持する変数
状態として管理する値は、Stateクラスのフィールドとして定義します。
たとえば、モデルの読み込みが完了しているかを真偽値で表す場合は、次のように記述できます。
bool _isModelReady = false;
エラー内容を保持する場合は、文字列を使用できます。
String _errorMessage = '';
ただし、読み込み中、完了、エラーという3種類の状態を複数の真偽値で管理すると、矛盾した状態が生まれる可能性があります。
読み込み中であり、同時に完了している
完了しているが、エラーも発生している
この問題を避けるため、次章ではenumを使って状態を一つの値として管理します。
10.4 setState()で画面を更新する
状態変数へ新しい値を代入しただけでは、画面は自動的に更新されません。変更を画面へ反映するには、setState()を使用します。
void _markModelReady() {
setState(() {
_isModelReady = true;
});
}
setState()の中で状態を変更すると、Flutterは該当するStateのbuild()を再実行します。
次のように、setState()の外側で値だけを変更した場合、内部の値は変わっても画面表示が更新されない可能性があります。
void _markModelReady() {
_isModelReady = true;
}
画面表示に関係する値を変更するときは、原則としてsetState()を使用します。
10.5 Widgetが再構築される仕組み
setState()を実行すると、Flutterはbuild()を再度呼び出し、現在の状態をもとにWidgetを作り直します。
@override
Widget build(BuildContext context) {
return Text(
_isModelReady ? '表示完了' : '読み込み中',
);
}
_isModelReadyがfalseなら「読み込み中」、trueなら「表示完了」が返されます。
再構築とは、画面全体を最初から描画し直すことではありません。Flutterは変更前後のWidgetを比較し、必要な部分だけを効率的に更新します。
そのため、build()の中には、通信処理やファイル保存などの重い処理を書かず、現在の状態から表示するWidgetを決める処理を記述します。
10.6 状態変更処理をメソッドへ分ける
状態変更をbuild()へ直接書き込むのではなく、役割ごとのメソッドへ分けます。
/// モデル読み込み完了後の状態を反映します。
///
/// 入力: なし
/// 出力: なし
void _handleModelLoaded() {
setState(() {
_isModelReady = true;
_errorMessage = '';
});
}
処理をメソッドへ分けると、次の点が明確になります。
- いつ状態が変わるのか
- どの値を変更するのか
- 何のイベントに対応する処理なのか
後の章では、読み込み完了、エラー、再読み込みをそれぞれ別のメソッドとして実装します。
10.7 mountedを確認する理由
3Dモデルの読み込みは、開始してから完了するまでに時間がかかります。その間に利用者が別画面へ移動すると、読み込み完了時点で元の画面が破棄されている可能性があります。
mountedは、現在のStateがまだWidgetツリーに存在しているかを表します。
void _handleModelLoaded() {
if (!mounted) {
return;
}
setState(() {
_isModelReady = true;
});
}
破棄された画面に対してsetState()を実行すると、実行時エラーの原因になります。時間のかかる処理やコールバックの後で画面を更新する場合は、mountedを確認します。
10.8 build()内で状態を変更しない
build()は、状態をもとに画面を作るためのメソッドです。原則として、build()の実行中にsetState()を呼び出してはいけません。
@override
Widget build(BuildContext context) {
// build中に状態を変更しない
setState(() {
_isModelReady = true;
});
return const SizedBox();
}
このような処理は、再構築中に再び再構築を要求し、エラーや不要な繰り返し処理につながります。
状態は、ボタン操作、読み込み完了、通信結果などのイベントを受け取ったタイミングで更新します。
10.9 この章の到達目標
この章の終了時点で、次の内容を確認します。
StatelessWidgetとStatefulWidgetの違いを説明できる- 変化する値を
Stateクラスへ保持できる setState()が必要な理由を説明できる- 状態変更後に
build()が再実行されることを理解している - 状態変更処理をメソッドへ分離できる
- 非同期処理後に
mountedを確認する理由を説明できる build()内で状態を変更してはいけない理由を理解している
この章では、状態管理の仕組みを確認したため、画面の見た目にはまだ大きな変更がありません。
次章では、読み込み中、表示完了、エラーを表すViewerStatusを定義し、3Dビューアーの状態を安全に管理します。