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

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

はじめに

provider を利用した状態の監視について、watch select の違いを理解し適切に使用したい初学者向けの記事になります!

watchselect の違い

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 を使うべきか

  1. パフォーマンスの向上:必要なプロパティの変更時のみウィジェットが再構築されるため、無駄な処理を減らせます
  2. メモリ効率:不要な再構築を避けることでメモリ使用量が減少します
  3. より予測可能な動作:ウィジェットがいつ再構築されるかが明確になります

実際の使用例

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が青くふわっとアニメーションをする実装を加えています!

  1. 映画を選択すると
    • MovieSelectorWidgetselectedMovieId を監視しているため再構築されます
    • WatchListWidgetMovieModel 全体を監視しているため、不要にも関わらず再構築されます
  2. ウォッチリストを変更すると
    • MovieSelectorWidgetwatchList を監視していないため再構築されません
    • WatchListWidgetwatchList を含むモデル全体を監視しているため再構築されます

おわりに

状態管理においては、「必要なものだけを監視する」という原則が重要です。この記事を参考に select もつかっていきましょう! (`・ω・´)

Anycloudではプロダクト開発の支援を行っています

プロダクト開発をお考えの方はぜひAnycloudにご相談ください。

まずは相談する

記事を書いた人

Tsukamura

エンジニア

Tsukamura

株式会社Anycloudでエンジニアをしています。