# State Management Flutter di 2026: Riverpod vs Bloc vs GetX — Panduan Lengkap > Perbandingan praktis solusi state management Flutter di tahun 2026. Riverpod 3.0, Bloc 9.0, dan GetX dievaluasi dengan contoh kode nyata, analisis performa, dan strategi migrasi. - Published: 2026-04-02 - Updated: 2026-04-02 - Author: Anthony Fillion-Maillet - Tags: flutter, state-management, riverpod, bloc, getx, dart - Reading time: 10 min --- State management pada Flutter menentukan cara aplikasi mengelola aliran data antar widget. Di tahun 2026, tiga solusi mendominasi ekosistem: Riverpod 3.0 dengan keamanan compile-time, Bloc 9.0 dengan kemampuan pelacakan event tingkat enterprise, dan GetX yang penggunaannya semakin menurun namun masih hadir di berbagai proyek. Pemilihan solusi yang tepat berdampak langsung pada testability, skalabilitas, dan biaya pemeliharaan jangka panjang. > **Kerangka Keputusan Cepat** > > Riverpod 3.0 cocok untuk sebagian besar proyek berkat keamanan compile-time dan boilerplate yang minimal. Bloc 9.0 tetap menjadi standar untuk industri teregulasi yang membutuhkan audit trail berbasis event. GetX sebaiknya hanya dipertimbangkan untuk memelihara codebase yang sudah ada tanpa anggaran migrasi. ## Riverpod 3.0: Keamanan Compile-Time dan Auto-Retry Riverpod 3.0 memperkenalkan perubahan mendasar dalam cara aplikasi Flutter mendeklarasikan dan mengonsumsi state. Code generation berbasis anotasi menangkap kesalahan dependensi pada saat kompilasi, bukan saat runtime, sehingga menghilangkan seluruh kategori bug yang sebelumnya memerlukan pengujian manual. Mekanisme auto-retry untuk provider yang gagal menangani error jaringan sementara tanpa intervensi manual. Ketika komputasi provider gagal, Riverpod secara otomatis mencoba ulang dengan delay yang dapat dikonfigurasi, mengurangi kode boilerplate untuk pemulihan error. ```dart // counter_provider.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'counter_provider.g.dart'; // Code generation ensures compile-time safety @riverpod class Counter extends _$Counter { @override int build() => 0; // Initial state void increment() => state = state + 1; void decrement() => state = state - 1; void reset() => state = 0; } ``` Anotasi `@riverpod` menghasilkan semua boilerplate provider secara otomatis. Ketidakcocokan tipe, override yang hilang, dan dependensi sirkuler terdeteksi selama proses kompilasi. ```dart // user_repository_provider.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'user_repository_provider.g.dart'; @riverpod Future currentUser(Ref ref) async { final authService = ref.watch(authServiceProvider); final userId = authService.currentUserId; // Auto-retry on network failure (Riverpod 3.0) final response = await ref.watch( httpClientProvider, ).get('/api/users/$userId'); return User.fromJson(response.data); } ``` Riverpod 3.0 juga secara otomatis menjeda listener provider ketika widget meninggalkan layar, mengurangi komputasi yang tidak perlu dan meningkatkan efisiensi baterai pada perangkat mobile. ## Bloc 9.0: Arsitektur Event-Driven untuk Aplikasi Enterprise Bloc 9.0 menerapkan pemisahan ketat antara event, state, dan business logic. Setiap perubahan state dipetakan ke event tertentu, menciptakan audit trail yang dibutuhkan oleh industri teregulasi. Pemeriksaan keamanan mounted dalam versi 9.0 mencegah callback dieksekusi pada widget yang sudah di-dispose. ```dart // authentication_event.dart sealed class AuthenticationEvent {} final class LoginRequested extends AuthenticationEvent { final String email; final String password; LoginRequested({required this.email, required this.password}); } final class LogoutRequested extends AuthenticationEvent {} final class SessionRestored extends AuthenticationEvent { final String token; SessionRestored({required this.token}); } ``` Sealed class pada Dart 3 menjamin exhaustive pattern matching pada event. Compiler memastikan bahwa setiap tipe event memiliki handler yang sesuai. ```dart // authentication_bloc.dart import 'package:flutter_bloc/flutter_bloc.dart'; class AuthenticationBloc extends Bloc { final AuthRepository _authRepo; final TokenStorage _tokenStorage; AuthenticationBloc({ required AuthRepository authRepo, required TokenStorage tokenStorage, }) : _authRepo = authRepo, _tokenStorage = tokenStorage, super(AuthenticationInitial()) { on(_onLoginRequested); on(_onLogoutRequested); on(_onSessionRestored); } Future _onLoginRequested( LoginRequested event, Emitter emit, ) async { emit(AuthenticationLoading()); try { final token = await _authRepo.login( email: event.email, password: event.password, ); await _tokenStorage.save(token); emit(AuthenticationSuccess(token: token)); } catch (e) { emit(AuthenticationFailure(message: e.toString())); } } Future _onLogoutRequested( LogoutRequested event, Emitter emit, ) async { await _tokenStorage.clear(); emit(AuthenticationInitial()); } Future _onSessionRestored( SessionRestored event, Emitter emit, ) async { emit(AuthenticationSuccess(token: event.token)); } } ``` Setiap event handler menghasilkan transisi state yang jelas. Logging middleware dapat mencatat setiap event untuk keperluan debugging atau kepatuhan regulasi. Interface `EmittableStateStreamableSource` pada Bloc 9.0 menyederhanakan pengujian dengan memungkinkan implementasi mock yang ringan. ## Event Transformer Bloc: Menangani Input Frekuensi Tinggi Bloc menyediakan event transformer bawaan yang menyelesaikan masalah concurrency umum. Pencarian saat mengetik, ketukan tombol berulang, dan stream data real-time semuanya mendapat manfaat dari pemrosesan event secara deklaratif. ```dart // search_bloc.dart import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class SearchBloc extends Bloc { final SearchRepository _repository; SearchBloc({required SearchRepository repository}) : _repository = repository, super(SearchInitial()) { // restartable() cancels previous search on new input on( _onQueryChanged, transformer: restartable(), ); // droppable() ignores events while processing on( _onResultSelected, transformer: droppable(), ); } Future _onQueryChanged( SearchQueryChanged event, Emitter emit, ) async { if (event.query.length < 3) { emit(SearchInitial()); return; } emit(SearchLoading()); final results = await _repository.search(event.query); emit(SearchLoaded(results: results)); } Future _onResultSelected( SearchResultSelected event, Emitter emit, ) async { emit(SearchNavigating(result: event.result)); } } ``` Transformer `restartable()` membatalkan pencarian yang sedang berjalan ketika input baru tiba, mencegah hasil yang sudah usang menimpa hasil yang lebih baru. Transformer `droppable()` mengabaikan ketukan duplikat selama navigasi sedang berlangsung. ## GetX: Utang Teknis dan Realita Migrasi GetX mendapatkan popularitas melalui kecepatan prototyping dan boilerplate yang minimal. Di tahun 2026, library ini menghadapi krisis pemeliharaan: pembaruan yang sporadis, ketergantungan pada satu maintainer, dan inkompatibilitas yang terus bertambah dengan versi terbaru Flutter SDK. Aplikasi produksi yang menggunakan GetX mengalami masalah lifecycle controller dan kebocoran memori akibat singleton global yang implisit. ```dart // counter_controller.dart (GetX pattern) import 'package:get/get.dart'; // Global singleton - difficult to test and scope class CounterController extends GetxController { final count = 0.obs; // Reactive observable void increment() => count.value++; void decrement() => count.value--; // Lifecycle hooks - disposal timing is unpredictable @override void onClose() { // Cleanup may not execute reliably super.onClose(); } } // Usage in widget class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { // Get.put creates a global singleton final controller = Get.put(CounterController()); return Obx(() => Text('${controller.count}')); } } ``` Pemanggilan `Get.put()` mendaftarkan controller sebagai singleton global. Dalam alur navigasi yang kompleks, controller bertahan melampaui cakupan yang dimaksudkan, sehingga mengonsumsi memori. Variabel reaktif `.obs` melewati sistem notifikasi state standar Flutter, membuat integrasi dengan paket lain menjadi tidak andal. ## Migrasi dari GetX ke Riverpod: Langkah demi Langkah Bagi tim yang memelihara codebase GetX, migrasi ke Riverpod dapat dilakukan secara bertahap. Kedua library dapat berdampingan dalam proyek yang sama, memungkinkan konversi layar per layar tanpa perlu menulis ulang seluruh aplikasi. ```dart // Step 1: Replace GetX controller with Riverpod notifier // Before (GetX) class ProductController extends GetxController { final products = [].obs; final isLoading = false.obs; Future loadProducts() async { isLoading.value = true; products.value = await ProductApi.fetchAll(); isLoading.value = false; } } // After (Riverpod 3.0) @riverpod class ProductList extends _$ProductList { @override Future> build() async { // Auto-retry on failure, auto-pause when off-screen return ProductApi.fetchAll(); } Future refresh() async { ref.invalidateSelf(); } } ``` ```dart // Step 2: Replace widget bindings // Before (GetX) class ProductPage extends StatelessWidget { @override Widget build(BuildContext context) { final ctrl = Get.put(ProductController()); return Obx(() { if (ctrl.isLoading.value) return CircularProgressIndicator(); return ListView.builder( itemCount: ctrl.products.length, itemBuilder: (_, i) => ProductTile(ctrl.products[i]), ); }); } } // After (Riverpod 3.0) class ProductPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final productsAsync = ref.watch(productListProvider); return productsAsync.when( loading: () => const CircularProgressIndicator(), error: (err, stack) => ErrorDisplay(error: err), data: (products) => ListView.builder( itemCount: products.length, itemBuilder: (_, i) => ProductTile(products[i]), ), ); } } ``` Versi Riverpod menangani state loading, error, dan data secara eksplisit melalui `AsyncValue.when()`. Tidak ada singleton global, tidak ada manajemen lifecycle manual, dan disposal otomatis ketika widget di-unmount. ## Perbandingan Performa: Efisiensi Rebuild Efisiensi rebuild berdampak langsung pada frame rate. Setiap solusi menangani rebuild widget secara berbeda, dan perbedaannya menjadi terukur pada daftar dengan ratusan item. | Metrik | Riverpod 3.0 | Bloc 9.0 | GetX | |--------|-------------|----------|------| | Rebuild selektif | `select()` filter | `BlocSelector` | `.obs` per field | | Keamanan compile-time | Penuh (code gen) | Parsial (sealed classes) | Tidak ada | | Auto-dispose | Bawaan | Manual via `close()` | Tidak andal | | Jeda saat off-screen | Otomatis (3.0) | Manual | Tidak didukung | | Pelacakan event | Provider observer | Log event lengkap | Tidak ada | | Isolasi pengujian | `ProviderContainer.test()` | `EmittableStateStreamableSource` | Memerlukan `Get.testMode` | | Dampak ukuran bundle | ~45KB | ~38KB | ~120KB (termasuk routing, DI, HTTP) | Metode `select()` Riverpod dan `BlocSelector` Bloc sama-sama memungkinkan rebuild yang presisi, hanya memperbarui subtree widget yang bergantung pada data yang berubah. `.obs` GetX mencapai granularitas serupa per field tetapi tidak memiliki verifikasi compile-time terhadap grafik dependensi. > **Ukuran Bundle GetX** > > GetX menggabungkan routing, dependency injection, HTTP client, dan state management dalam satu paket. Aplikasi yang hanya menggunakan state management tetap mengimpor seluruh library sebesar 120KB. Riverpod dan Bloc adalah paket terfokus yang melakukan satu hal dengan baik. ## Strategi Pengujian untuk Setiap Solusi Testability sering menentukan solusi mana yang dapat berkembang seiring pertumbuhan tim. Setiap library memiliki pendekatan pengujian yang berbeda. ```dart // Riverpod test - isolated container import 'package:flutter_test/flutter_test.dart'; import 'package:riverpod/riverpod.dart'; void main() { test('Counter increments', () { final container = ProviderContainer.test(); // Override dependencies for isolation final counter = container.read(counterProvider.notifier); expect(container.read(counterProvider), 0); counter.increment(); expect(container.read(counterProvider), 1); }); } ``` ```dart // Bloc test - event-driven verification import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { blocTest( 'emits [loading, success] on valid login', build: () => AuthenticationBloc( authRepo: MockAuthRepo(), tokenStorage: MockTokenStorage(), ), act: (bloc) => bloc.add( LoginRequested(email: 'dev@test.com', password: 'secure123'), ), expect: () => [ isA(), isA(), ], ); } ``` `ProviderContainer.test()` dari Riverpod membuat grafik dependensi yang terisolasi per pengujian. Helper `blocTest` dari Bloc memverifikasi urutan transisi state secara tepat, sesuai dengan arsitektur event-driven. Pengujian GetX memerlukan pengaturan `Get.testMode = true` dan pengelolaan lifecycle controller secara manual, yang sering menyebabkan pengujian yang tidak stabil di lingkungan CI. > **Persiapan Wawancara Kerja** > > State management Flutter termasuk topik yang paling sering ditanyakan dalam wawancara developer mobile. Memahami trade-off antara Riverpod, Bloc, dan GetX menunjukkan kematangan arsitektural. Berlatihlah menjelaskan kapan setiap solusi cocok dan kapan tidak cocok digunakan. ## Matriks Keputusan: Memilih Solusi yang Tepat Kendala proyek menentukan pilihan terbaik. Ukuran tim, persyaratan regulasi, dan codebase yang sudah ada semuanya menjadi faktor dalam pengambilan keputusan. **Riverpod 3.0** cocok ketika tim mengutamakan keamanan compile-time, proyek membutuhkan pengambilan data async dengan pemulihan error otomatis, atau codebase dibangun dari awal. Kurva pembelajaran bersifat moderat: developer yang sudah familiar dengan Provider dapat bertransisi secara alami. **Bloc 9.0** cocok ketika proyek beroperasi di industri teregulasi (fintech, kesehatan), tim membutuhkan pelacakan event lengkap untuk audit, atau aplikasi menangani alur kerja concurrent yang kompleks seperti pemrosesan pembayaran. Biaya boilerplate terbayar dengan kemudahan pemeliharaan dalam skala besar. **GetX** hanya cocok untuk memelihara codebase GetX yang sudah ada di mana biaya migrasi melebihi anggaran yang tersedia. Memulai proyek baru dengan GetX di tahun 2026 berarti menciptakan utang teknis sejak hari pertama. [Dokumentasi resmi Flutter](https://docs.flutter.dev/data-and-backend/state-mgmt/options) tidak mencantumkan GetX di antara solusi yang direkomendasikan. Untuk latihan lebih mendalam tentang pola state management Flutter, modul [dasar-dasar state management Flutter](/technologies/flutter/interview-questions/state-management-basics) membahas konsep-konsep fundamental yang diuji dalam wawancara. Modul [pola provider](/technologies/flutter/interview-questions/provider-pattern) mengeksplorasi strategi dependency injection yang berlaku untuk ketiga solusi. ## Kesimpulan - Riverpod 3.0 menyediakan keamanan compile-time melalui code generation, retry otomatis untuk provider yang gagal, dan dukungan pause/resume yang mengurangi konsumsi baterai pada perangkat mobile - Bloc 9.0 menerapkan transisi state berbasis event dengan kemampuan audit penuh, menjadikannya standar untuk aplikasi enterprise di industri teregulasi - GetX menghadapi krisis pemeliharaan di tahun 2026 dengan pembaruan yang sporadis dan inkompatibilitas SDK yang semakin bertambah; proyek GetX yang sudah ada perlu merencanakan migrasi bertahap ke Riverpod - Migrasi dari GetX ke Riverpod dilakukan layar per layar tanpa perlu menulis ulang seluruh aplikasi, karena kedua library dapat berdampingan dalam proyek yang sama - Isolasi pengujian berbeda secara signifikan: Riverpod menggunakan `ProviderContainer.test()`, Bloc menggunakan `blocTest` dengan verifikasi urutan event, dan GetX memerlukan konfigurasi test mode global yang rentan - Ukuran bundle penting pada mobile: Riverpod (~45KB) dan Bloc (~38KB) menyediakan paket yang terfokus, sementara GetX (~120KB) membawa fitur-fitur yang tidak terpakai --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/flutter/flutter-state-management-2026-riverpod-bloc-getx