Riverpod: StateProvider
State sederhana yang bisa diubah: counter dengan StateProvider.
Kenapa Butuh StateProvider?
Provider biasa itu immutable, nilainya paten selama app berjalan. Tapi aplikasi penuh dengan nilai yang berubah: angka counter, tab yang sedang aktif, mode gelap atau terang, filter kategori yang dipilih. Dulu solusinya setState, tapi setState cuma hidup di satu widget. Begitu dua widget berbeda (misal badge angka di AppBar dan tombol di body) butuh nilai yang sama, setState mentok.
StateProvider adalah jawaban paling simpel: state yang bisa diubah, bisa dibaca dari widget mana pun, dan setiap perubahan otomatis me-rebuild widget yang menontonnya. Anggap saja setState yang bisa dipakai lintas widget.
Contoh 1: Counter Klasik
final counterProvider = StateProvider<int>((ref) => 0);
class CounterPage extends ConsumerWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(title: const Text('Counter')),
body: Center(
child: Text('$count', style: const TextStyle(fontSize: 64)),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: const Icon(Icons.add),
),
);
}
}Ada tiga peran yang berbeda di sini: ref.watch(counterProvider) membaca nilai sekaligus berlangganan perubahan, ref.read(...) dipakai di callback supaya tidak ikut berlangganan, dan .notifier memberikan akses ke pengontrol state untuk mengubah nilainya.
Contoh 2: Kasus Nyata, Pilihan Kategori
Contoh yang lebih dekat ke aplikasi warkop: chip kategori yang dipilih dipakai di dua tempat sekaligus (barisan chip dan daftar produk di bawahnya):
final kategoriProvider = StateProvider<String>((ref) => 'Semua');
class MenuPage extends ConsumerWidget {
const MenuPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final kategori = ref.watch(kategoriProvider);
const daftar = ['Semua', 'Kopi', 'Non-Kopi', 'Snack'];
return Column(
children: [
Wrap(
children: [
for (final k in daftar)
ChoiceChip(
label: Text(k),
selected: k == kategori,
onSelected: (_) =>
ref.read(kategoriProvider.notifier).state = k,
),
],
),
Expanded(child: DaftarProduk(kategori: kategori)),
],
);
}
}Satu baris state = k mengubah nilai, dan semua widget yang watch langsung rebuild. Tidak ada callback yang dioper-oper ke bawah, tidak ada setState di parent yang me-rebuild semuanya.
Pola Baca-Tulis Lengkap
// Baca + langganan (di dalam build):
final count = ref.watch(counterProvider);
// Tulis nilai baru:
ref.read(counterProvider.notifier).state = 10;
// Ubah berdasarkan nilai lama (lebih aman):
ref.read(counterProvider.notifier).update((s) => s + 1);
// Reset ke awal:
ref.read(counterProvider.notifier).state = 0;Pakai update kalau nilai baru tergantung nilai lama (tambah atau kurang). Ini menghindari race condition saat dua event datang berurutan dan keduanya membaca nilai lama yang sama.
Edge Case
- Jangan ubah state di dalam
build. Memanggil.state = xsaat build berjalan memicu rebuild baru di tengah rebuild: infinite loop. Ubah state hanya dari event (onPressed, onSelected) atau dari efek samping yang terkontrol. ref.watchme-rebuild seluruh widget. Kalau widget-mu besar, pecah bagian yang menonton provider ke widget kecil tersendiri (atau bungkus denganConsumer) supaya rebuild-nya murah dan tidak mengulang layout berat.- StateProvider itu untuk nilai primitif dan sederhana. Di Riverpod 2.x ia dianggap legacy untuk kasus kompleks. Kalau state-mu berupa object dengan banyak field plus method, naik kelas ke
NotifierProvider.
Catatan teknis:
.notifierpada StateProvider mengembalikanStateController. Di Riverpod versi terbaru, pola yang direkomendasikan untuk logika kompleks adalahNotifierProvider, tapi StateProvider tetap cara tercepat untuk boolean, int, String, atau enum sederhana.
Kapan JANGAN Pakai StateProvider
- State berupa object kompleks (keranjang belanja dengan method tambah, hapus, dan hitung total): pakai
NotifierProvidersupaya logika terkumpul di satu class, bukan tersebar di banyak onPressed. - Data async dari API: StateProvider tidak mengenal konsep loading dan error. Pakai
FutureProvideratauAsyncNotifier. - State yang cuma dipakai di satu widget:
setStatelebih jujur dan lebih sedikit boilerplate. Jangan pakai meriam untuk membunuh nyamuk.
Tantangan
Counter StateProvider
Buat counter dengan StateProvider<int>: tombol + menambah, tombol - mengurangi, dan tombol reset ke 0. Tampilkan angka besar di tengah.