BAB 38. STATE MANAGEMENT FLUTTER (RIVERPOD)
Alasan Memilih Riverpod
| Keunggulan | Penjelasan |
|---|---|
| Compile-time safe | Error terdeteksi saat kompilasi, bukan runtime |
| Testable | Provider dapat di-mock dengan mudah |
Tidak bergantung BuildContext | Dapat diakses dari mana saja |
| Modular | Setiap feature punya provider sendiri |
| Auto-dispose | Memory dibebaskan otomatis saat tidak dipakai |
Struktur Provider per Feature
features/request/providers/
├── request_list_provider.dart # Daftar request
├── create_request_provider.dart # Form state + submit
├── request_detail_provider.dart # Detail satu request
└── request_status_provider.dart # Stream status request
Jenis Provider yang Digunakan
FutureProvider — Data Async Sekali Ambil
// Mengambil daftar request dari API
final requestListProvider = FutureProvider.autoDispose<List<RequestEntity>>((ref) async {
final useCase = ref.watch(getRequestsUseCaseProvider);
return useCase.call();
});
StateNotifierProvider — State yang Bisa Berubah
// State form pembuatan request
class CreateRequestController extends StateNotifier<AsyncValue<void>> {
final CreateRequestUseCase _useCase;
CreateRequestController(this._useCase) : super(const AsyncValue.data(null));
Future<void> submit(CreateRequestParams params) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => _useCase.call(params));
}
}
final createRequestProvider = StateNotifierProvider.autoDispose<
CreateRequestController, AsyncValue<void>>((ref) {
return CreateRequestController(ref.watch(createRequestUseCaseProvider));
});
StreamProvider — Data Realtime
// Stream status request secara realtime
final requestStatusProvider = StreamProvider.family<RequestStatus, int>((ref, requestId) {
final service = ref.watch(realtimeServiceProvider);
return service.watchRequestStatus(requestId);
});
Arsitektur Riverpod Per Feature
flowchart TD
Page["📱 Page\n(ConsumerWidget)"] --> CP["Controller Provider\n(StateNotifier)"]
CP --> UCP["Use Case Provider"]
UCP --> UC["Use Case"]
UC --> RP["Repository Provider"]
RP --> REPO["Repository Impl"]
REPO --> DSP["Datasource Provider"]
DSP --> DS["Remote Datasource\n(Dio HTTP)"]
style Page fill:#4f63d2,color:#fff
style UC fill:#7c3aed,color:#fff
style DS fill:#059669,color:#fff
Struktur Lengkap Providers
providers/
├── auth_provider.dart # Token, user session
├── profile_provider.dart # Profil & skill siswa
├── request_provider.dart # Request list, create, detail
├── booking_provider.dart # Booking accept/reject
├── chat_provider.dart # Pesan realtime
├── meeting_provider.dart # Check-in, check-out
├── leaderboard_provider.dart # Ranking
└── notification_provider.dart # Daftar notifikasi
Global State (App-Wide)
State yang dibutuhkan di seluruh aplikasi dikelola di core/providers/:
// core/providers/auth_provider.dart
final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
return AuthNotifier(ref.watch(authRepositoryProvider));
});
// core/providers/school_provider.dart
final schoolContextProvider = Provider<SchoolContext>((ref) {
final auth = ref.watch(authStateProvider);
return auth.user?.schoolContext ?? SchoolContext.empty();
});
Async State Handling
Menggunakan AsyncValue untuk menangani semua kondisi loading/error/data:
// Di dalam Page widget
ref.watch(requestListProvider).when(
loading: () => const LoadingOverlay(),
error: (error, stack) => ErrorView(message: error.toString()),
data: (requests) => RequestListView(requests: requests),
);
Testing Provider
// test/features/request/request_provider_test.dart
void main() {
test('createRequest should update state to loading then success', () async {
final container = ProviderContainer(
overrides: [
createRequestUseCaseProvider.overrideWithValue(
MockCreateRequestUseCase(),
),
],
);
final controller = container.read(createRequestProvider.notifier);
await controller.submit(mockParams);
expect(
container.read(createRequestProvider),
const AsyncValue<void>.data(null),
);
});
}