StateNotifierとStatefulWidgetを使い分ける
StatefulWidgetとは
Flutterの基本となるWidgetの一つです。類似するものにStatelessWidgetがあります。
Stateが静的なStatelessWidgetに対してStateが動的なWidgetがStatefulWidgetです。
StateのフィールドはStatefulWidgetを継承したWidgetのメンバ変数として定義します。
Stateを更新する際はsetStateを呼び出してそのメソッド内でメンバ変数を更新します。
StateNotifierとは
Flutterで状態をProviderやRiverpodを使用して管理する際に組み合わせて利用するモジュールです。類似するものにChangeNotifierなどがあります。
State内で定義されているフィールドはimmutableにしてフィールドの更新はfreezedのcopyWithメソッドなどを利用しディープコピーで更新します。
Stateを更新したときの動作
Stateを更新するとそのWidgetと子Widgetのbuildメソッドが実行されUIが再構築されます。
(const修飾子を付けたWidgetはbuildメソッドは呼ばれません)
なので画面全体でStateを管理してしまうと更新する必要のないWidgetも更新されてしまうため注意が必要です。
実際以下のようなStateNotifierとStateNotifierProviderを利用したStateの管理の実装をしばしば目にします。
class HomeScreen extends StatelessWidget {
void build(BuildContext context) {
return StateNotifierProvider<HomeStateNotifier, HomeState>(
create: () => HomeStateNotifier(),
builder: (context) {
return Scaffold(
...
);
}
);
}
}
この実装の場合、 HomeStateのフィールドが更新された場合、builder内で作成されているWIdgetが再生成されるため予期しない動作を生む原因になります。
Stateを適切に管理するには
Stateを適切に管理するにはWidgetを細かく分けて実装しWidget同士が依存しあわないようにする必要があります。
例えば以下のような画面があったとします。
この画面ではユーザーを一覧表示し、ユーザーのセルではユーザーのアイコン、ユーザー名、お気に入りボタンが表示されています。
ユーザー一覧はAPIから取得することを想定しています。
この画面で表示するWidgetを細かく分けると以下のような構成になるかと思います。
この画面の構成要素でStateの管理が必要なWidgetはListViewとFavoriteButtonのみになります。
ListViewではユーザーの一覧情報をStateで管理しFavoriteButtonではお気に入りに登録されているかどうかをStateで管理します。
今回の場合ではListViewではStateNotifierを利用し、FavoriteIconはStatefulWidgetを利用してStateを管理します。こうすることでユーザーの一覧に変更があったときはListViewが再生成され、お気に入り状態が変更された際はFavoriteIconだけ再生成されるようになります。
このようにWidget単位でStateを管理してあげることで余計なbuildメソッドが呼ばれてしまうことを防ぐことができます。
使い分けるときのポイント
使い分けるときのポイントは管理したいStateの特徴で分けると良いです。
Widgetそのものの状態だけを管理したい場合はStatefulWidgetを使い、APIなどから非同期で取得するデータがStateに含まれているときやWidgetをまたいで同じStateを管理したいときはStateNotifierを利用すると効果的です。
まとめ
StateNotifierは強力で便利なモジュールなので乱用してしまいがちですが場合によってはオーバースペックになり不具合を生んでしまう原因にもなりうるため注意が必要です。
StateNotifierとStatefulWidgetを組み合わせて使うことでより効率的に実装を進めることができると思うので参考にしてみていただければ幸いです。
Discussion