riverpodrefstateMenengah4 mnt baca

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

dart
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.watch di dalam onPressed. Riverpod akan melempar error karena watch hanya boleh dipanggil saat build berjalan. Aturannya sederhana: tampilkan pakai watch, aksi pakai read.

Contoh Nyata: listen untuk Navigasi, select untuk Performa

listen ideal untuk reaksi satu kali: user menekan bayar, status jadi sukses, aplikasi pindah halaman.

dart
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:

dart
// 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

  • read di dalam build adalah bug yang sunyi: jalan tanpa error, tapi UI tak pernah update karena tak ada langganan. Kalau nilainya ditampilkan, selalu watch.
  • listen aman dipanggil di build: Riverpod mengurus subscription mengikuti lifecycle widget, tanpa tutup manual.
  • Jangan ref.watch di callback listen (untuk efek samping); butuh nilai provider lain? Pakai ref.read.
  • Hati-hati context setelah jeda async: kalau callback meng-await lalu memakai context, cek context.mounted dulu.
  • Selector select harus 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 initState pakai ref.read, bukan watch (belum tersedia, akan error). Logika reaksi pindahkan ke listen di build.

Kapan Memakai Apa: Ringkasan

  • Butuh nilai sekali di initState atau event handler: jangan watch, pakai read.
  • Butuh me-render data yang reaktif: jangan read di build, pakai watch.
  • Butuh efek samping (navigasi, snackbar, log analytics): jangan lakukan langsung di build (akan jalan tiap rebuild!), pakai listen.
  • Widget hanya butuh satu field kecil dari state besar dan rebuild-nya mahal: jangan watch mentah-mentah, pakai select.

Tantangan

Snackbar dengan ref.listen

Buat StateProvider<String> pesan. Setiap pesan berubah, tampilkan SnackBar otomatis via ref.listen. Tambahkan tombol yang mengubah pesan.