【Flutter Provider入門②】ChangeNotifier と notifyListeners() を理解する

前回はProviderが「なぜ必要なのか」を学びました。
今回はProviderの核心、「どうやって画面を自動更新するのか」 の仕組みを理解します。


目次

今回の主役は2つ

ChangeNotifier   ← 「変化を通知できるクラス」にする魔法
notifyListeners() ← 「変わったよ!」と全画面に叫ぶメソッド

この2つがわかると、「なぜ画面が自動更新されるのか」がスッキリ理解できます。


ChangeNotifier って何?

クラスに extends ChangeNotifier と書くだけで、そのクラスは
「データが変わったことを画面に知らせる能力」 を持てます。

// ChangeNotifierなし → ただのクラス
class StudentViewModel {
  List<Student> _students = [];
}

// ChangeNotifierあり → 「通知できるクラス」になる!
class StudentViewModel extends ChangeNotifier {
  List<Student> _students = [];
}

extends ChangeNotifier を書くだけでOKです。
難しい処理を自分で書く必要はありません。


notifyListeners() って何?

データを変更したあとに notifyListeners() を呼ぶと、
「データが変わったよ!」と、このデータを使っている全画面に通知 が飛びます。

class StudentViewModel extends ChangeNotifier {
  List<Student> _students = [];
  List<Student> get students => _students;

  // 生徒を追加するメソッド
  void addStudent(Student student) {
    _students.add(student);
    notifyListeners(); // ← ここがポイント!「変わったよ!」と通知
  }
}

通知を受け取った画面は 自動的に再描画 されます。


図で流れを理解する

① ボタンを押す
      ↓
② addStudent() が呼ばれる
      ↓
③ _students.add(student) でデータ追加
      ↓
④ notifyListeners() で「変わったよ!」と通知
      ↓
⑤ このデータを使っている画面が自動更新 ✨

notifyListeners() を書き忘れると、データは追加されているのに画面が更新されません。
以前「画面が更新されない」と困っていた原因の多くはこれです!


実際のコードで確認する

月謝袋アプリで「生徒を追加する」機能を例に見てみます。

ViewModel(通知する側)

class StudentViewModel extends ChangeNotifier {
  // _ がついているのは「外から直接変更させない」ため
  List<Student> _students = [];

  // 外からは get で読み取り専用で公開
  List<Student> get students => _students;

  // 生徒追加
  void addStudent(String name, int fee) {
    final student = Student(name: name, monthlyFee: fee);
    _students.add(student);
    notifyListeners(); // ← これがないと画面が更新されない!
  }

  // 生徒削除
  void removeStudent(int index) {
    _students.removeAt(index);
    notifyListeners(); // ← 削除後も通知が必要
  }
}

View(通知を受け取る側)

class StudentListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // context.watch → データの変化を「監視」する
    final vm = context.watch<StudentViewModel>();

    return Scaffold(
      body: ListView.builder(
        itemCount: vm.students.length,
        itemBuilder: (_, index) => ListTile(
          title: Text(vm.students[index].name),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // context.read → 「実行するだけ」で監視しない
          context.read<StudentViewModel>().addStudent('田中さん', 5000);
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

_ アンダースコアは何のため?

List<Student> _students = []; // _ がついている
List<Student> get students => _students; // _ なしで公開

Dartでは変数名の先頭に _ をつけると クラスの外から直接変更できなくなります。

// 外からの直接変更を防ぐ
vm._students.add(student); // ❌ エラー!

// getterを通して読み取りはOK
print(vm.students); // ✅ これはOK

なぜこうするのかというと…

❌ 外から直接変更 → notifyListeners()が呼ばれない → 画面が更新されない😱
✅ メソッド経由  → notifyListeners()が必ず呼ばれる → 画面が更新される✨

「データ変更は必ずViewModelのメソッド経由で行う」 というルールを守るための仕組みです。


よくあるミスと対処法

ミス① notifyListeners() を書き忘れる

void addStudent(Student student) {
  _students.add(student);
  // notifyListeners(); ← 書き忘れ!→ 画面が更新されない😱
}

データを変更するメソッドには 必ず最後に notifyListeners() を書く 習慣をつけましょう。


ミス② StatelessWidget なのに watch が更新されない?

context.watch を使っていれば StatelessWidget でも自動更新されます。
StatefulWidget にする必要はありません。

// StatelessWidget でも大丈夫!
class StudentListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final vm = context.watch<StudentViewModel>(); // これで自動更新される
    // ...
  }
}

ミス③ build の外で watch を使う

// ❌ NG:build の外で watch を使わない
class StudentListPage extends StatelessWidget {
  final vm = context.watch<StudentViewModel>(); // エラー!

  @override
  Widget build(BuildContext context) { ... }
}

// ✅ OK:build の中で使う
class StudentListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final vm = context.watch<StudentViewModel>(); // ここでOK
    // ...
  }
}

今回のまとめ

extends ChangeNotifier
  → クラスに「通知する能力」を与える

notifyListeners()
  → データを変えたあとに呼ぶ「更新通知」
  → 書き忘れると画面が更新されない!

_ アンダースコア
  → 外から直接変更されないようにする
  → notifyListeners() を必ず通すための工夫

次回予告

次回は context.watch と context.read の違いを解説します。

context.watch<StudentViewModel>() // 監視する
context.read<StudentViewModel>()  // 読むだけ(実行するだけ)

「どっちを使えばいいの?」という混乱を完全に解消します!


このシリーズの全体構成

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

Flutter / Provider / ChangeNotifier / 初心者 / アプリ開発

目次