MVVM (Model-View-ViewModel)
MVVM adalah pattern yang digunakan pada Presentation Layer untuk memisahkan UI dari logic.
Diagram
Rules
- View hanya mengobservasi state dari ViewModel dan mengirim events/actions.
- ViewModel memegang UI state dan memanggil Use Cases. Tidak boleh memiliki referensi ke View.
- ViewModel tidak boleh import framework UI (
android.view.*,UIKit,flutter/widgets.dart). - Satu screen = satu ViewModel (kecuali ada shared state yang jelas).
UI State Pattern
Semua platform wajib menggunakan sealed/union state class untuk representasi UI state:
- Kotlin
- Swift
- Dart (Freezed)
- TypeScript
sealed interface HomeUiState {
data object Loading : HomeUiState
data class Success(
val users: List<UserUiModel>,
val isRefreshing: Boolean = false,
) : HomeUiState
data class Error(
val message: String,
val retryAction: (() -> Unit)? = null,
) : HomeUiState
}
enum HomeUiState {
case loading
case success(users: [UserUiModel], isRefreshing: Bool = false)
case error(message: String)
}
sealed class HomeState with _$HomeState {
const factory HomeState.loading() = _Loading;
const factory HomeState.success({
required List<UserUiModel> users,
(false) bool isRefreshing,
}) = _Success;
const factory HomeState.error(String message) = _Error;
}
type HomeUiState =
| { status: 'loading' }
| { status: 'success'; users: UserUiModel[]; isRefreshing: boolean }
| { status: 'error'; message: string };
Anti-pattern
Jangan gunakan multiple boolean flags (isLoading, isError, hasData) secara terpisah. Gunakan sealed state class untuk menghindari impossible states.
// ❌ Bad
var isLoading = false
var isError = false
var data: List<User>? = null
// Kemungkinan: isLoading=true AND isError=true? 🤔
// ✅ Good
var state: HomeUiState = HomeUiState.Loading