[Flutter] Scoped Model 入門
Scoped ModelはFlutterで用いられる状態管理手法の一つです。現在Googleが開発しているFuchsiaというOS内でScoped Modelの考え方が使われています。scoped_modelパッケージはFuchsia内のソースコードを抽出して作られたパッケージです。Scoped Modelは他の状態管理手法に比べて学習コストが低く、プロジェクトへ導入しやすい状態管理手法です。
Scoped Modelの特徴
- 下位ツリーのウィジェットから上位ツリーに配置されているモデルクラスオブジェクトにアクセスすることができる。
- モデルクラスオブジェクトが更新された時にモデルクラスオブジェクトを参照している下位ツリーのウィジェットをリビルドさせることができる。
実装
カウンターアプリをScoped Modelを用いて開発してみます。画面中央にカウント数を表す数字が三つ表示され、フローティングボタンをタップするとカウント数がインクリメントされるシンプルなアプリです。説明のため、縦に並んでいる数字の中で真ん中のカウント数右横にはアイコンを表示しています。
ソースコードは以下に置いています。
パッケージのインストール
scoped_modelパッケージをインストールするため、pubspec.yamlに以下のコードを記述します。
モデルクラスの作成
上位ツリーのウィジェットと下位ツリーのウィジェットで共有するモデルクラスを作成します。
モデルクラスはModel
クラスをextends
します。今回作成したモデルクラスではカウント数を保持するためのint
型変数_counter、カウント数をインクリメントするincrementメソッドを定義しました。また、モデルクラスオブジェクトを参照しているウィジェットをリビルドさせる時はnotifyListeners()
メソッドをコールします。今回はincrementメソッド内にnotifyListeners()
メソッドを記述しています。このようにすることでカウント数がインクリメントされる度に、Counterオブジェクトを参照しているウィジェットをリビルドさせることができます。
ScopedModelウィジェットを配置
下位ツリーのウィジェットからモデルクラスオブジェクトへアクセスできるようにするため、上位ツリーにScopedModel<T>
ウィジェットを配置します。
ScopedModel<T>
は下位ツリーのウィジェットにモデルクラスオブジェクトを渡せるようにするためのウィジェットです。<T>にはモデルクラスの型を指定します。model
引数にはモデルクラスオブジェクトを指定し、child
引数には表示したいウィジェットを指定します。
下位ツリーからモデルクラスオブジェクトにアクセス
下位ツリーからモデルクラスオブジェクトにアクセスする手段はScopedModelDescendant<T>
ウィジェットを使用する方法とScopedModel.of<T>(context, rebuildOnChange: )
メソッドを使用する方法があります。
ScopedModelDescendant<T>
ScopedModelDescendant<T>
ウィジェットの引数に指定したビルダー関数の引数からモデルクラスオブジェクトにアクセスすることができます。model
には<T>で指定したモデルクラスオブジェクト、child
にはScopedModelDescendant<T>
のchild
引数に指定したウィジェットが渡ってきます。モデルクラスオブジェクトに変更が入った時にScopedModelDescendant<T>
ウィジェットの引数に指定したビルダー関数はリビルドされます。つまりモデルクラスオブジェクトに変更が入った時に、ビルダー関数で生成するウィジェットの表示を更新することができます。このように、StatefulWidgetを使用しなくてもStatelessWidgetで画面の表示を更新することができます。
しかし、ビルダー関数で生成するウィジェット内にリビルド対象外としたいウィジェットが含まれる時があります。このような場合にScopedModelDescendant<T>
ウィジェットのchild
引数にリビルド対象外としたいウィジェットを指定します。このchild
引数に指定したウィジェットはビルダー関数の引数child
として渡ってきます。このchild
で指定されたウィジェットはリビルド対象外となるのでビルダー関数内にビルド対象外としたいウィジェットを含むことができます。以下のソースはScopedModelDescendant<T>
ウィジェットのchild
引数にウィジェットを指定したソースコードです。
ScopedModelDescendant<T>
ウィジェットのchild
引数にWidgetAを指定しています。モデルクラスオブジェクトであるCounterに変更が入った時、builder
引数に指定したビルダー関数はリビルドされますが、WidgetAはリビルド対象外とすることができます。
ScopedModel.of<T>(context, rebuildOnChange: )
ScopedModel.of<T>(context, rebuildOnChange: )
メソッドを使用することでScopedModel<T>
ウィジェットのmodel
引数に指定したモデルクラスオブジェクトにアクセスすることができます。rebuildOnChange
をtrue
にするとモデルクラスオブジェクトに変更が入った時に、モデルクラスオブジェクトにアクセスしたウィジェットがリビルドされます。
実行結果
フローティングボタンをタップする度に画面中央に表示されている数字がインクリメントされています。
また、WidgetAのbuild
メソッドにprint('Build WidgetA')を記述していますが、フローティングボタンをタップしても'Build WidgetA'がコンソールに表示されていません。つまり、WidgetAはリビルドされていないことがわかります。また、ScopedModel.of<T>(context, rebuildOnChange: )
メソッドでモデルクラスオブジェクトにアクセスしているWidgetBはフローティングボタンをタップする度に'Build WidgetB'がコンソールに表示されていることから毎回リビルドされていることがわかります。
Discussion