Lewati ke konten utama

BAB 38. STATE MANAGEMENT FLUTTER (RIVERPOD)

Alasan Memilih Riverpod

KeunggulanPenjelasan
Compile-time safeError terdeteksi saat kompilasi, bukan runtime
TestableProvider dapat di-mock dengan mudah
Tidak bergantung BuildContextDapat diakses dari mana saja
ModularSetiap feature punya provider sendiri
Auto-disposeMemory 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),
);
});
}