🔭

[Flutter] Scoped Model 入門

2022/05/08に公開約3,800字

Scoped ModelはFlutterで用いられる状態管理手法の一つです。現在Googleが開発しているFuchsiaというOS内でScoped Modelの考え方が使われています。scoped_modelパッケージはFuchsia内のソースコードを抽出して作られたパッケージです。Scoped Modelは他の状態管理手法に比べて学習コストが低く、プロジェクトへ導入しやすい状態管理手法です。

https://youtu.be/j4SwkdgORZ8

Scoped Modelの特徴

  • 下位ツリーのウィジェットから上位ツリーに配置されているモデルクラスオブジェクトにアクセスすることができる。
  • モデルクラスオブジェクトが更新された時にモデルクラスオブジェクトを参照している下位ツリーのウィジェットをリビルドさせることができる。

実装

カウンターアプリをScoped Modelを用いて開発してみます。画面中央にカウント数を表す数字が三つ表示され、フローティングボタンをタップするとカウント数がインクリメントされるシンプルなアプリです。説明のため、縦に並んでいる数字の中で真ん中のカウント数右横にはアイコンを表示しています。

ソースコードは以下に置いています。

https://github.com/NAOYA-MAEDA-DEV/flutter_scoped_model_sample

パッケージのインストール

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 引数に指定したモデルクラスオブジェクトにアクセスすることができます。rebuildOnChangetrue にするとモデルクラスオブジェクトに変更が入った時に、モデルクラスオブジェクトにアクセスしたウィジェットがリビルドされます。

実行結果

フローティングボタンをタップする度に画面中央に表示されている数字がインクリメントされています。

また、WidgetAのbuild メソッドにprint('Build WidgetA')を記述していますが、フローティングボタンをタップしても'Build WidgetA'がコンソールに表示されていません。つまり、WidgetAはリビルドされていないことがわかります。また、ScopedModel.of<T>(context, rebuildOnChange: ) メソッドでモデルクラスオブジェクトにアクセスしているWidgetBはフローティングボタンをタップする度に'Build WidgetB'がコンソールに表示されていることから毎回リビルドされていることがわかります。

Discussion

ログインするとコメントできます