Dulu saya pakai Provider. Bukan karena pilihan yang disengaja banget — lebih karena itu yang paling banyak diajarkan tutorial waktu saya mulai belajar Flutter, dan ya, langsung saya pakai.
Bertahan beberapa proyek. Tidak ada masalah besar. Tapi ada satu proyek yang cukup kompleks — tiga layar saling berbagi state, ada beberapa operasi async yang perlu dikoordinasi — di mana saya mulai merasakan ketidaknyamanan yang tidak bisa saya abaikan lagi.
Itu titik saya mulai serius lirik Riverpod.
Masalah Konkret yang Saya Hadapi dengan Provider
Provider itu bagus. Tapi ada beberapa hal yang mengganggu di proyek yang lebih kompleks:
ChangeNotifier yang terlalu tahu banyak. Saya sering akhirkan punya ChangeNotifier yang menyimpan terlalu banyak state karena tidak ada cara mudah untuk memecahnya tanpa kehilangan kemudahan akses. Hasilnya: satu class besar yang susah ditest dan susah di-maintain.
Tidak bisa akses provider di luar widget tree. Kalau butuh akses state di luar widget — misalnya dari service atau utility class — harus akali dengan cara yang tidak elegan.
Exception runtime yang tidak informatif. Kalau provider belum ada di tree ketika widget mencoba mengaksesnya, error yang muncul kadang tidak langsung menunjuk ke akar masalah.
Pertama Kali Coba Riverpod: Confusing
Jujur, pertama kali buka dokumentasi Riverpod 2.0 saya cukup bingung. Terlalu banyak tipe provider — Provider, StateProvider, FutureProvider, StreamProvider, NotifierProvider, AsyncNotifierProvider. Rasanya seperti harus belajar dari awal lagi.
Yang membantu saya: saya coba paksa diri untuk pahami satu tipe provider dulu sampai benar-benar mengerti kapan digunakannya, sebelum pindah ke tipe berikutnya.
Mulai dari yang paling sederhana: Provider biasa untuk dependency injection.
// Di Riverpod, provider adalah object global
final apiServiceProvider = Provider<ApiService>((ref) {
return ApiService(baseUrl: 'https://api.kawunganten.com');
});
final artikelRepositoryProvider = Provider<ArtikelRepository>((ref) {
// ref.watch() untuk mengakses provider lain
final api = ref.watch(apiServiceProvider);
return ArtikelRepository(api: api);
});
Ini sudah langsung terasa lebih bersih dari Provider biasa — tidak ada ChangeNotifier yang perlu dibuat untuk dependency injection sederhana.
FutureProvider: Penanganan Async yang Saya Suka
Yang paling mengubah cara saya menulis kode Flutter adalah FutureProvider:
final daftarArtikelProvider = FutureProvider<List<Artikel>>((ref) async {
final repository = ref.watch(artikelRepositoryProvider);
return repository.getSemuaArtikel();
});
// Di widget:
class BerandaPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final artikelAsync = ref.watch(daftarArtikelProvider);
return artikelAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Gagal memuat: $error')),
data: (daftar) => ListView.builder(
itemCount: daftar.length,
itemBuilder: (context, index) => ArtikelCard(artikel: daftar[index]),
),
);
}
}
Tidak ada lagi boilerplate manual untuk handle loading state, tidak ada try-catch yang tersebar di mana-mana, tidak ada notifyListeners() yang harus dipanggil. Semuanya sudah ditangani oleh framework.
NotifierProvider untuk Logika Bisnis yang Kompleks
Untuk state yang butuh logika — bukan sekadar fetch data — saya pakai NotifierProvider:
@riverpod
class FormArtikel extends _$FormArtikel {
@override
FormArtikelState build() {
return const FormArtikelState(
judul: '',
konten: '',
kategoriId: null,
isSaving: false,
);
}
void setJudul(String judul) {
state = state.copyWith(judul: judul);
}
void setKonten(String konten) {
state = state.copyWith(konten: konten);
}
Future<void> simpan() async {
if (state.judul.isEmpty || state.konten.isEmpty) return;
state = state.copyWith(isSaving: true);
try {
final repo = ref.read(artikelRepositoryProvider);
await repo.simpanArtikel(
judul: state.judul,
konten: state.konten,
kategoriId: state.kategoriId,
);
// Reset form setelah berhasil
state = build();
} catch (e) {
state = state.copyWith(isSaving: false);
rethrow;
}
}
}
Yang Membuat Saya Tidak Mau Balik ke Provider
Setelah beberapa bulan pakai Riverpod secara konsisten, ada beberapa hal yang saya tidak mau lepas:
Testing yang bersih. Karena provider adalah object global yang independen dari widget tree, testing jauh lebih mudah — tidak perlu setup pohon widget yang rumit hanya untuk test logika bisnis.
Override yang elegan. Untuk testing atau untuk environment development/production yang berbeda, Riverpod punya mekanisme override yang sangat clean.
// Di test:
final container = ProviderContainer(
overrides: [
apiServiceProvider.overrideWithValue(MockApiService()),
],
);
// Sekarang semua provider yang bergantung pada apiServiceProvider
// otomatis menggunakan MockApiService
Compile-time safety. Mencoba mengakses provider yang belum ada akan menghasilkan error saat compile, bukan saat runtime.
Kapan Saya Masih Pakai Provider?
Untuk proyek yang sudah jalan dengan Provider dan tidak ada masalah signifikan, saya tidak akan paksa migrasi. Migrasi itu butuh waktu dan ada risiko regresi.
Untuk proyek baru, saya mulai langsung dengan Riverpod. Dan untuk proyek lama yang mulai terasa semakin susah di-maintain — saya migrasi bertahap, feature by feature, bukan sekaligus.
Kalau Anda masih di tahap mempertimbangkan mau pakai yang mana, pengalaman saya: kalau proyeknya kecil dan sederhana, Provider masih fine. Tapi kalau sudah anticipate proyek yang akan berkembang, investasikan waktu untuk belajar Riverpod dari awal. Lebih mudah mulai dengan yang lebih powerful dari pada migrasi nanti.
Ada yang punya cerita migrasi state management Flutter yang menarik? Atau pertanyaan soal Riverpod yang spesifik? Kolom komentar ada di bawah.
Belum ada komentar. Jadilah yang pertama menulis.
Tulis Komentar