【Flutter初心者向け】providerを利用したwatchとselectの違いとパフォーマンス向上のポイント

はじめに
provider を利用した状態の監視について、watch select の違いを理解し適切に使用したい初学者向けの記事になります!
watch
と select
の違い
context.watch<T>()
- モデル全体の変更を監視
- モデルのどの部分が変更されても、ウィジェットが再構築される
- 簡単に使えるが、不必要な再構築を引き起こす可能性がある
// MovieModel全体を監視
final model = context.watch<MovieModel>();
final selectedMovieId = model.selectedMoviewId;
context.select<T, R>(R selector(T value))
- モデルの特定のプロパティのみを監視
- 選択したプロパティが変更された場合のみ、ウィジェットが再構築される
- パフォーマンスの向上につながる
// selectedMovieIdプロパティのみを監視
final selectedMovieId = context.select((MovieModel model) => model.selectedMovieId);
なぜ select
を使うべきか
- パフォーマンスの向上:必要なプロパティの変更時のみウィジェットが再構築されるため、無駄な処理を減らせます
- メモリ効率:不要な再構築を避けることでメモリ使用量が減少します
- より予測可能な動作:ウィジェットがいつ再構築されるかが明確になります
実際の使用例
MovieSelectorWidget と WatchListWidget を例に、select
, watch
を使用したときのウィジェットの更新状態について見ていきます。
ユーザーは利用可能な映画リストから映画を選択し、ウォッチリストに追加・削除できます。
select
を使ったMovieSelectorWidget
このウィジェットは select
を使って、selectedMovieId
プロパティのみを監視しています。そのため、ウォッチリストが変更されても、このウィジェットは再構築されません。これにより、必要な状態変更時のみ画面が更新されます。
// selectedMovieIdプロパティのみを監視
class MovieSelectorWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final selectedMovieId = context.select((MovieModel model) => model.selectedMovieId);
// 読み取りのみなので、watch, selectではなくreadを使用
final availableMovies = context.read<MovieModel>().availableMovies;
return ListView.builder(
itemCount: availableMovies.length,
itemBuilder: (context, index) {
final movie = availableMovies[index];
final isSelected = movie.id == selectedMovieId;
return ListTile(
title: Text(movie.title),
selected: isSelected,
onTap: () {
context.read<MovieModel>().selectMovie(movie.id);
},
);
},
);
}
}
watch
を使ったWatchListWidget
このウィジェットは watch
を使って、MovieModel
全体を監視しています。つまり、このウィジェットが実際に必要としていない状態(例:selectedMovieId
)が変更された場合でも、ウィジェット全体が再構築されます。これはデモンストレーションのために意図的に非効率な実装になっています。
class WatchListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// MovieModel全体を監視
final model = context.watch<MovieModel>();
final watchList = model.watchList;
final selectedMovieId = model.selectedMovieId;
final availableMovies = model.availableMovies;
return ListView.builder(
itemCount: watchList.length,
itemBuilder: (context, index) {
final movieId = watchList[index];
final movie = availableMovies.firstWhere((m) => m.id == movieId);
return ListTile(
title: Text(movie.title),
trailing: IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
model.toggleWatchList(movie.id);
},
),
);
},
);
}
}
実際のアプリでの挙動の違い
以下の動画は上記の2つのWidgetをベースにUIを整えたアプリです。Widgetの再構築のタイミングでWidgetが青くふわっとアニメーションをする実装を加えています!
- 映画を選択すると
MovieSelectorWidget
はselectedMovieId
を監視しているため再構築されますWatchListWidget
もMovieModel
全体を監視しているため、不要にも関わらず再構築されます
- ウォッチリストを変更すると
MovieSelectorWidget
はwatchList
を監視していないため再構築されませんWatchListWidget
はwatchList
を含むモデル全体を監視しているため再構築されます
おわりに
状態管理においては、「必要なものだけを監視する」という原則が重要です。この記事を参考に select
もつかっていきましょう! (`・ω・´)