Riverpod: ref.watch vs ref.read vs ref.listen
Kapan memakai watch, read, listen, dan select.
Kenapa Ada Tiga Cara Akses
ref adalah pintu masuk ke semua provider, tapi cara membukanya menentukan perilaku widget. Salah pilih, bug-nya halus dan menyebalkan: UI tidak update, crash saat tombol ditekan, atau snackbar muncul berkali-kali.
watch: rebuild widget setiap berubah. Untuk menampilkan data.read: beri nilainya sekali, tanpa langganan. Untuk event handler.listen: panggil callback setiap berubah, tanpa rebuild. Untuk efek samping (snackbar, navigasi).
Ditambah select: "aku hanya peduli satu field dari object besar." Untuk performa rebuild.
Contoh Dasar: Ketiganya dalam Satu Layar
final counterProvider = StateProvider<int>((ref) => 0);
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider); // tampilkan, rebuild otomatis
ref.listen<int>(counterProvider, (prev, next) { // efek samping, bukan render
if (next == 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Sampai 10!')));
}
});
return Scaffold(
body: Center(
child: Text('$count', style: const TextStyle(fontSize: 48)),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++, // read di callback
child: const Icon(Icons.add),
),
);
}
}Catatan teknis: Kesalahan paling umum pemula adalah memakai
ref.watchdi dalamonPressed. Riverpod akan melempar error karenawatchhanya boleh dipanggil saatbuildberjalan. Aturannya sederhana: tampilkan pakaiwatch, aksi pakairead.
Contoh Nyata: listen untuk Navigasi, select untuk Performa
listen ideal untuk reaksi satu kali: user menekan bayar, status jadi sukses, aplikasi pindah halaman.
class BayarPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<AsyncValue<String>>(bayarProvider, (prev, next) {
next.whenOrNull(
data: (noStruk) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => StrukPage(no: noStruk)),
);
},
error: (err, _) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Pembayaran gagal: $err')),
);
},
);
});
final status = ref.watch(bayarProvider);
return Scaffold(
body: status.when(
data: (_) => const Text('Menunggu pembayaran...'),
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Text('Error: $e'),
),
floatingActionButton: FloatingActionButton(
onPressed: () =>
ref.read(bayarProvider.notifier).proses(totalBelanja),
child: const Icon(Icons.payment),
),
);
}
}Navigasi/snackbar tidak ditaruh langsung di build (berjalan berkali-kali); listen menjamin callback hanya jalan saat nilai benar-benar berubah.
Dan select mencegah rebuild mahal. Contoh header yang hanya menampilkan jumlah item:
// Tanpa select: header rebuild setiap kali qty item berubah,
// padahal yang ditampilkan cuma jumlah item.
final jumlah = ref.watch(keranjangProvider.select((list) => list.length));Widget ini hanya rebuild saat length berubah. Untuk list ratusan item, bedanya terasa.
Edge Case dan Praktik Terbaik
readdi dalambuildadalah bug yang sunyi: jalan tanpa error, tapi UI tak pernah update karena tak ada langganan. Kalau nilainya ditampilkan, selaluwatch.listenaman dipanggil dibuild: Riverpod mengurus subscription mengikuti lifecycle widget, tanpa tutup manual.- Jangan
ref.watchdi callbacklisten(untuk efek samping); butuh nilai provider lain? Pakairef.read. - Hati-hati
contextsetelah jeda async: kalau callback meng-await lalu memakaicontext, cekcontext.mounteddulu. - Selector
selectharus murah dan stabil.select((u) => u.nama)bagus;select((list) => list.where(...).toList())buruk karena membuat list baru tiap evaluasi sehingga widget rebuild terus. - Di
initStatepakairef.read, bukanwatch(belum tersedia, akan error). Logika reaksi pindahkan kelistendibuild.
Kapan Memakai Apa: Ringkasan
- Butuh nilai sekali di
initStateatau event handler: janganwatch, pakairead. - Butuh me-render data yang reaktif: jangan
readdibuild, pakaiwatch. - Butuh efek samping (navigasi, snackbar, log analytics): jangan lakukan langsung di
build(akan jalan tiap rebuild!), pakailisten. - Widget hanya butuh satu field kecil dari state besar dan rebuild-nya mahal: jangan
watchmentah-mentah, pakaiselect.
Tantangan
Snackbar dengan ref.listen
Buat StateProvider<String> pesan. Setiap pesan berubah, tampilkan SnackBar otomatis via ref.listen. Tambahkan tombol yang mengubah pesan.