Riverpod: Provider Dasar
State management modern: setup, Provider, dan ConsumerWidget.
Masalah yang Diselesaikan Riverpod
Di aplikasi kecil, oper data lewat constructor masih oke. Tapi saat membesar kamu kena "prop drilling": data dioper dari kakek ke bapak ke anak ke cucu, padahal yang butuh cuma si cucu. Setiap widget di tengah meneruskan parameter yang tak dipakai; ubah satu nama, semuanya ikut berubah.
Riverpod menyelesaikan ini dengan menyediakan nilai secara global tapi aman: widget mana pun bisa "menonton" nilai tanpa perantara, dan yang rebuild hanya widget yang menonton, bukan seluruh tree.
Kenapa Riverpod, bukan package provider biasa? Tiga alasan: provider dideklarasikan di luar widget (bebas error "Provider not found"), 100% aman saat compile (salah tipe ketahuan sebelum jalan), dan mudah di-test karena bisa di-override.
Setup
flutter pub add flutter_riverpodBungkus seluruh app dengan ProviderScope, sekali saja di main:
void main() {
runApp(const ProviderScope(child: MyApp()));
}Lupa langkah ini adalah error nomor satu pemula: crash "ProviderScope not found" saat widget pertama yang memakai provider di-build.
Contoh 1: Provider Nilai Sederhana
// Deklarasikan di top-level file, di luar widget:
final namaTokoProvider = Provider<String>((ref) => 'Warkop Senja');
final diskonProvider = Provider<double>((ref) => 0.1);
class Sapaan extends ConsumerWidget {
const Sapaan({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final nama = ref.watch(namaTokoProvider);
final diskon = ref.watch(diskonProvider);
return Text('Selamat datang di $nama (diskon ${(diskon * 100).toInt()}%)');
}
}ConsumerWidget sama seperti StatelessWidget tapi method build-nya menerima WidgetRef tambahan. ref.watch membuat widget otomatis rebuild kalau nilai provider berubah. Provider biasa bersifat immutable: nilainya tetap selama app berjalan, cocok untuk konfigurasi, repository, atau service.
Contoh 2: Provider untuk Service (Pola Dunia Nyata)
Pola paling berguna dalam praktik: daftarkan class service atau API client sebagai provider, supaya gampang diganti saat testing:
final apiClientProvider = Provider<ApiClient>((ref) {
return ApiClient(baseUrl: 'https://api.warkop.com');
});
class DaftarMenu extends ConsumerWidget {
const DaftarMenu({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final api = ref.watch(apiClientProvider);
// pakai api.ambilMenu() di FutureBuilder / FutureProvider ...
return const Placeholder();
}
}
// Saat testing, override dengan mock:
ProviderScope(
overrides: [
apiClientProvider.overrideWithValue(ApiClientPalsu()),
],
child: const DaftarMenu(),
)Dengan overrideWithValue, widget-mu tak tahu bedanya API asli dan palsu. Inilah kenapa Riverpod disebut test-friendly.
watch vs read vs listen
ref.watch(p): dipakai di dalambuild. Widget berlangganan dan rebuild otomatis saat nilai berubah.ref.read(p): dipakai di dalam callback sepertionPressed. Baca sekali, tidak berlangganan, tidak memicu rebuild.ref.listen(p, (prev, next) {...}): dipakai untuk efek samping saat nilai berubah (tampilkan SnackBar, pindah halaman), tanpa rebuild.
Kesalahan klasik: memanggil ref.watch di dalam onPressed. Itu tidak me-rebuild apa-apa dan Riverpod akan protes. Di callback selalu pakai read.
Catatan teknis: Deklarasikan provider sebagai variabel top-level
final, jangan di dalambuildatau di dalam class widget. Provider yang dibuat di dalam build akan dibuat ulang setiap rebuild dan state-nya hilang begitu saja.
Edge Case
ref.watchdiinitStateitu dilarang. Di initState belum ada build yang bisa di-rebuild. Pakairef.read, atau pakaiConsumerStatefulWidgetdan akses ref dididChangeDependencies.- Provider yang dideklarasikan di dalam fungsi build menghasilkan provider baru setiap rebuild. Selalu taruh di top-level file.
- Lupa
ProviderScopemembuat semua provider crash saat runtime, bukan saat compile. Kalau ada error provider yang misterius, cek main.dart dulu.
Kapan JANGAN Pakai Provider
- Untuk state yang berubah-ubah (counter, form, tab aktif),
Providerbiasa tidak bisa karena immutable. PakaiStateProvideratauNotifierProvider(modul berikutnya). - Untuk data async (hasil API), jangan bungkus Future mentah di Provider. Pakai
FutureProvideryang punya.when()untuk menangani loading, error, dan data dengan rapi. - Untuk logika yang cuma dipakai satu widget kecil,
setStatebiasa lebih simpel dan jujur. Riverpod bersinar saat state dipakai lintas banyak widget.
Tantangan
Provider konfigurasi
Buat Provider<String> berisi nama tokomu, lalu tampilkan di AppBar memakai ConsumerWidget + ref.watch.