ChangeNotifierとProviderでアプリケーションの状態を共有し、狙いを定めたリビルドのためにwatch、read、selectを使いこなしましょう。
このレッスンを Kodokon で開く状態が単一の画面とともに生まれて消える限り、setStateで十分です。データが共有される、つまりカート、セッション、設定といったものになった瞬間、それをコンストラクタからコンストラクタへと渡し続けるのは手に負えなくなります。ChangeNotifierはSDKの最小限のオブザーバブルを提供します。providerはそれをツリーに注入し、購読しているウィジェットだけをリビルドします。RiverpodやBlocと比べると、この組み合わせは依然として最も監査しやすいものです。魔法はほとんどなく、依存関係は一つ、一文で説明できる仕組みです。
flutter pub add providerうまく設計されたnotifierはカプセル化します。プライベートなフィールド、読み取り専用のゲッター、そしてnotifyListeners()で終わるメソッドを通じてのみ行う変更です。この契約こそが、どんな状態の変化もUIをすり抜けないことを保証します。
import 'package:flutter/foundation.dart';
class CartModel extends ChangeNotifier {
final List<String> _items = [];
List<String> get items => List.unmodifiable(_items);
int get count => _items.length;
void add(String item) {
_items.add(item);
notifyListeners();
}
void remove(String item) {
_items.remove(item);
notifyListeners();
}
}三つの入り口、三つの用途です。context.watch<T>()はウィジェットを購読させます。notifyListenersのたびにリビルドします。context.read<T>()は購読せずに読み取ります。onPressedのようなコールバックのための道具です。context.select<T, R>()は射影だけを購読します。その正確な値が変わったときにだけウィジェットがリビルドします。黄金律は、watchとselectはbuildのなかだけ、readはハンドラーのなか、決してその逆はしない、です。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CounterModel extends ChangeNotifier {
int _value = 0;
int get value => _value;
void increment() {
_value++;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => CounterModel(),
child: const CounterApp(),
),
);
}
class CounterApp extends StatelessWidget {
const CounterApp({super.key});
@override
Widget build(BuildContext context) {
final value = context.watch<CounterModel>().value;
return MaterialApp(
home: Scaffold(
body: Center(child: Text('Count: $value')),
floatingActionButton: FloatingActionButton(
onPressed: () =>
context.read<CounterModel>().increment(),
child: const Icon(Icons.add),
),
),
);
}
}import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CartModel extends ChangeNotifier {
final List<String> _items = [];
int get count => _items.length;
}
class CartBadge extends StatelessWidget {
const CartBadge({super.key});
@override
Widget build(BuildContext context) {
final count = context.select(
(CartModel cart) => cart.count,
);
return Badge(
label: Text('$count'),
child: const Icon(Icons.shopping_cart),
);
}
}
onPressedのなかで、無用な購読を作らずにcart.add(...)を発火させるにはどうしますか?context.watch<CartModel>().add(...)context.read<CartModel>().add(...)context.select((CartModel c) => c.add(...))notifyListeners()は正確には何を引き起こしますか?_itemsを直接ではなくList.unmodifiable(_items)を公開するのはなぜですか?notifyListenersなしに、したがってリビルドなしに状態を変えてしまう外部からの変更を防ぐため。