Riverpod Dasar: Provider dan Consumer
Kelola state lintas halaman dengan Riverpod: Provider, FutureProvider, dan perbedaan ref.watch vs ref.read.
Kenapa state management?
setState hanya hidup di satu widget. Begitu data seperti keranjang belanja atau user login dibutuhkan banyak halaman, meneruskan lewat constructor menjadi mimpi buruk. State management memisahkan data dari UI: data tinggal di satu tempat, widget mana pun bisa membaca dan bereaksi saat data berubah.
Setup Riverpod
Tambahkan dependensi dan bungkus aplikasi dengan ProviderScope:
dependencies:
flutter_riverpod: ^2.5.0void main() {
runApp(const ProviderScope(child: MyApp()));
}Tanpa ProviderScope di root, semua provider akan error saat dibaca.
Provider paling sederhana
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Provider untuk nilai yang tidak berubah
final namaTokoProvider = Provider<String>((ref) => 'Warkop Doa Ambu');
// Membaca di widget
class HeaderToko extends ConsumerWidget {
const HeaderToko({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final nama = ref.watch(namaTokoProvider);
return Text(nama, style: const TextStyle(fontSize: 20));
}
}ConsumerWidget mirip StatelessWidget tapi build-nya menerima WidgetRef. Untuk stateful, ada ConsumerStatefulWidget dengan ConsumerState.
ref.watch vs ref.read
Ini konsep yang wajib dipahami:
ref.watch(provider): berlangganan. Widget rebuild otomatis saat nilai provider berubah. Pakai di dalambuild.ref.read(provider): baca sekali tanpa berlangganan. Pakai di event handler sepertionPressed.
ElevatedButton(
onPressed: () {
// Aksi: pakai read, bukan watch
final nama = ref.read(namaTokoProvider);
debugPrint('Toko: $nama');
},
child: const Text('Cek'),
)Memakai watch di dalam onPressed adalah error umum yang menyebabkan rebuild tak perlu atau warning lint.
FutureProvider untuk data async
Untuk data dari API, FutureProvider membungkus Future menjadi AsyncValue yang rapi:
final daftarProdukProvider = FutureProvider<List<Produk>>((ref) {
return repository.ambilProduk();
});
// Di UI
class Daftar extends ConsumerWidget {
const Daftar({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncProduk = ref.watch(daftarProdukProvider);
return asyncProduk.when(
data: (produk) => ListView.builder(
itemCount: produk.length,
itemBuilder: (context, i) => ListTile(title: Text(produk[i].nama)),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('Gagal: $e')),
);
}
}Method .when memaksa kamu menangani tiga state: data, loading, error. Tidak ada lagi lupa menangani error.
Catatan teknis: Provider bersifat global dan lazy: ia baru dibuat saat pertama kali di-watch. Jangan menaruh logika berat di body provider yang jalan di main thread tanpa disadari. Dan ingat,
ref.watchhanya boleh dipanggil di dalam build atau body provider lain, tidak di initState atau event handler. Butuh nilai di initState? Pakairef.readatau pindahkan ke provider.