riverpodfutureasyncMenengah5 mnt baca

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

dart
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 mengecek hasError.

Contoh Nyata: Refresh dan Detail per Item

Contoh yang lebih nyata: pull-to-refresh, tombol "coba lagi", dan halaman detail:

dart
// 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 error di .when() memang dirancang untuk menampilkannya. Fallback ditangani di UI, bukan dengan menyembunyikan kegagalan.
  • Future yang bergantung provider lain: panggil ref.watch di body provider sebelum await pertama, misalnya ambil token untuk header HTTP. Saat token berubah, future otomatis dijalankan ulang.
  • Awas ref setelah await. Provider bisa di-dispose saat request berjalan (user pindah halaman). Simpan yang dibutuhkan sebelum await pertama, atau cek ref.mounted.
  • Data mahal yang jarang berubah (konfigurasi, daftar provinsi): biarkan default (di-cache selama app hidup). Tambahkan .autoDispose hanya 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, lalu ref.invalidate provider 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: FutureBuilder biasa 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.