前回は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 / 初心者 / アプリ開発
