【Flutter Provider入門③】context.watch と context.read の違い

「どっちを使えばいいの?」と混乱しがちな2つのメソッド。
今回はこの違いを 完全に 理解します。


目次

結論を先に言います

context.watch  → 「監視する」変化があれば画面を更新する
context.read   → 「一回だけ読む(実行する)」更新はしない

これだけです。でも「どっちをいつ使うか」が最初はわかりにくいので、具体例で見ていきます。


テレビに例えて考える

context.watch → テレビを「つけっぱなし」にしている状態
                 何か変わればすぐ画面が変わる

context.read  → テレビを「一瞬だけチラ見」する状態
                 確認したらもう見ない

context.watch を使う場面

「データの表示」に使います。
データが変わったら画面も変わってほしい場所です。

class StudentListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    // watch → 生徒リストを「監視」している
    // 生徒が追加・削除されるたびに自動で画面が更新される
    final vm = context.watch<StudentViewModel>();

    return ListView.builder(
      itemCount: vm.students.length, // ← データが変わると自動更新
      itemBuilder: (_, index) => ListTile(
        title: Text(vm.students[index].name),
      ),
    );
  }
}

context.read を使う場面

「ボタンを押したときの処理」に使います。
「データを表示する必要はなく、メソッドを実行するだけ」の場所です。

FloatingActionButton(
  onPressed: () {
    // read → 「addStudentを実行するだけ」で監視はしない
    context.read<StudentViewModel>().addStudent('花子さん', 5000);
  },
  child: Icon(Icons.add),
),

なぜボタンに watch を使ってはいけないの?

// ❌ NG:ボタンに watch を使った場合
FloatingActionButton(
  onPressed: () {
    // watch を使うと、StudentViewModel が変わるたびに
    // このボタンも再描画される → 無駄な処理が増える!
    context.watch<StudentViewModel>().addStudent('花子さん', 5000);
  },
  child: Icon(Icons.add),
),

context.watch は build メソッドの中で使う のが基本です。
ボタンの onPressed の中で使うと無駄な再描画が起きます。


図で整理する

┌──────────────────────────────────────┐
│  build() の中 → context.watch を使う  │
│                                      │
│   Text(vm.students.length.toString())│ ← データ表示
│   ListView(vm.students)              │ ← リスト表示
│                                      │
│  ボタンの処理 → context.read を使う  │
│                                      │
│   onPressed: () {                    │
│     context.read<VM>().addStudent()  │ ← メソッド実行
│   }                                  │
└──────────────────────────────────────┘

まとめて書いたコードで確認

class StudentListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    // ✅ watch:表示に使う(監視する)
    final vm = context.watch<StudentViewModel>();

    return Scaffold(
      appBar: AppBar(
        title: Text('生徒一覧(${vm.students.length}人)'), // 自動更新される
      ),
      body: ListView.builder(
        itemCount: vm.students.length,
        itemBuilder: (_, index) => ListTile(
          title: Text(vm.students[index].name),
          trailing: IconButton(
            icon: Icon(Icons.delete),
            onPressed: () {
              // ✅ read:ボタン処理に使う(監視しない)
              context.read<StudentViewModel>().removeStudent(index);
            },
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // ✅ read:ボタン処理に使う(監視しない)
          context.read<StudentViewModel>().addStudent('新しい生徒', 5000);
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

もう一つ:context.select という便利なやつ

context.watch は ViewModel 全体を監視します。
ViewModel の一部だけを監視したいときは context.select が使えます。

// watch → StudentViewModel 全体を監視(何か変われば再描画)
final vm = context.watch<StudentViewModel>();

// select → 生徒の人数だけを監視(人数が変わったときだけ再描画)
final count = context.select<StudentViewModel, int>(
  (vm) => vm.students.length,
);

生徒数が変わったときだけ更新したい AppBar などに使うと効率的です。
ただし最初は watch と read だけ覚えれば十分です!


判断フローチャート

画面にデータを表示したい?
  ↓ YES
context.watch を使う ✅

ボタンを押したときにメソッドを実行したい?
  ↓ YES
context.read を使う ✅

どちらか迷ったら?
  ↓
「表示に関係している → watch」
「実行するだけ → read」と考える

今回のまとめ

context.watchcontext.read
使う場所build() の中(表示)onPressed など(処理)
監視するかする(変化で再描画)しない
覚え方見続ける(テレビをつけっぱなし)チラ見(一瞬だけ確認)

次回予告

最終回は MultiProvider と MVVM設計 を組み合わせて、アプリ全体の構造を整理する方法を解説します。

// こういう構造がスッキリ理解できるようになります!
List<SingleChildWidget> globalProviders = [
  ...independentModels,
  ...dependentModels,
  ...viewModels,
];

シリーズ全体の「まとめ回」です!


このシリーズの全体構成

  • 第1回:Providerって何?なぜ必要なの?
  • 第2回:ChangeNotifier と notifyListeners() を理解する
  • 第3回(今回):context.watch と context.read の違い
  • 第4回:MultiProvider と MVVM設計でアプリを整理する

Flutter / Provider / context.watch / context.read / 初心者 / アプリ開発

目次