Riverpod: FutureProvider
Data async tanpa FutureBuilder: FutureProvider + .when().
Kenapa FutureProvider Ada
Mengambil data dari API adalah pekerjaan paling umum di aplikasi Flutter, tapi FutureBuilder punya tiga jebakan klasik: Future yang dibuat di build() memicu ulang request HTTP setiap rebuild (solusi manualnya, simpan di initState, bikin kode bertele-tele); pengecekan snapshot.hasData/hasError/connectionState mudah lupa satu cabang tanpa protes compiler; dan me-refresh data atau memakai hasil yang sama di dua layar butuh plumbing manual.
FutureProvider menyelesaikan ketiganya: future dibuat dan di-cache sekali oleh provider, hasilnya diekspos sebagai AsyncValue yang type-safe, dan refresh bisa dipicu dari widget mana pun lewat ref.invalidate.
Contoh Dasar
final produkProvider = FutureProvider<List<Produk>>((ref) async {
final res = await http.get(Uri.parse('https://api.toko.id/produk'));
if (res.statusCode != 200) {
throw Exception('Gagal memuat produk (${res.statusCode})');
}
final list = jsonDecode(res.body) as List;
return list.map((e) => Produk.fromJson(e as Map<String, dynamic>)).toList();
});
class ProdukPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncProduk = ref.watch(produkProvider);
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: (err, stack) => Center(child: Text('Gagal memuat: $err')),
);
}
}Catatan teknis:
.when()memaksamu menangani ketiga cabang (data, loading, error) saat compile time. Tidak ada lagi layar putih karena lupa mengecekhasError.
Contoh Nyata: Refresh dan Detail per Item
Contoh yang lebih nyata: pull-to-refresh, tombol "coba lagi", dan halaman detail:
// Satu provider berparameter untuk tiap id produk.
final detailProdukProvider =
FutureProvider.family<Produk, int>((ref, id) async {
final res = await http.get(Uri.parse('https://api.toko.id/produk/$id'));
if (res.statusCode != 200) throw Exception('Produk tidak ditemukan');
return Produk.fromJson(jsonDecode(res.body) as Map<String, dynamic>);
});
class ProdukPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final asyncProduk = ref.watch(produkProvider);
return RefreshIndicator(
onRefresh: () => ref.refresh(produkProvider.future),
child: asyncProduk.when(
data: (produk) => ListView.builder(
itemCount: produk.length,
itemBuilder: (context, i) => ListTile(
title: Text(produk[i].nama),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DetailPage(id: produk[i].id),
),
),
),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, _) => Center(
child: ElevatedButton(
onPressed: () => ref.invalidate(produkProvider),
child: Text('Gagal: $err. Coba lagi'),
),
),
),
);
}
}Halaman detail tinggal ref.watch(detailProdukProvider(id)). ref.invalidate menandai provider kotor sehingga future dijalankan ulang; ref.refresh sama tapi langsung mengembalikan Future barunya, pas untuk onRefresh milik RefreshIndicator.
Edge Case dan Praktik Terbaik
- Jangan menelan error diam-diam. Biarkan exception naik ke provider; cabang
errordi.when()memang dirancang untuk menampilkannya. Fallback ditangani di UI, bukan dengan menyembunyikan kegagalan. - Future yang bergantung provider lain: panggil
ref.watchdi body provider sebelumawaitpertama, misalnya ambil token untuk header HTTP. Saat token berubah, future otomatis dijalankan ulang. - Awas
refsetelahawait. Provider bisa di-dispose saat request berjalan (user pindah halaman). Simpan yang dibutuhkan sebelumawaitpertama, atau cekref.mounted. - Data mahal yang jarang berubah (konfigurasi, daftar provinsi): biarkan default (di-cache selama app hidup). Tambahkan
.autoDisposehanya jika state memang harus dibuang saat tak ditonton. - Jangan pakai FutureProvider untuk aksi. Tombol "Bayar"/"Simpan" adalah perintah satu kali, bukan data. Panggil fungsi async dari
onPressed, laluref.invalidateprovider datanya.
Kapan Tidak Pakai
- Data real-time (chat, harga saham live, posisi kurir): pakai
StreamProvider, bukan future satu kali. - Future sekali pakai di satu widget kecil tanpa butuh refresh/sharing:
FutureBuilderbiasa sudah cukup. - Butuh init async plus method pengubah state (form yang load lalu bisa diedit): pakai
AsyncNotifierProvider. FutureProvider hanya untuk data read-only.
Tantangan
FutureProvider posts
Buat FutureProvider yang GET 10 judul dari JSONPlaceholder, tampilkan dengan .when(): loading spinner, error text, dan ListView data.