Bỏ qua để đến nội dung

ViewModel

ViewModel là class giữ và quản lý UI-related data theo cách lifecycle-aware.

  • Survive configuration changes (xoay màn hình)
  • Có scope lớn hơn Activity/Fragment
  • Tự động cleared khi owner destroyed
  • Không hold reference đến View/Context
class CounterViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value++
}
fun decrement() {
_count.value--
}
}
class MainActivity : ComponentActivity() {
// Lazy initialization
private val viewModel: CounterViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val count by viewModel.count.collectAsState()
Counter(
count = count,
onIncrement = viewModel::increment,
onDecrement = viewModel::decrement
)
}
}
}
class UserViewModel(
private val userId: Int,
private val repository: UserRepository
) : ViewModel() {
// ...
}
class UserViewModelFactory(
private val userId: Int,
private val repository: UserRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return UserViewModel(userId, repository) as T
}
}
// Usage
val viewModel: UserViewModel by viewModels {
UserViewModelFactory(userId, repository)
}
@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val userId: Int = savedStateHandle.get<Int>("userId") ?: 0
}
// Usage (no factory needed)
val viewModel: UserViewModel = hiltViewModel()

Lưu state survive process death:

class SearchViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Tự động save/restore
var searchQuery: String
get() = savedStateHandle.get<String>("query") ?: ""
set(value) = savedStateHandle.set("query", value)
// Hoặc dùng StateFlow
val searchQueryFlow: StateFlow<String> =
savedStateHandle.getStateFlow("query", "")
}

Chia sẻ data giữa Fragments:

// SharedViewModel
class SharedViewModel : ViewModel() {
private val _selectedItem = MutableStateFlow<Item?>(null)
val selectedItem: StateFlow<Item?> = _selectedItem.asStateFlow()
fun selectItem(item: Item) {
_selectedItem.value = item
}
}
// Fragment A
class ListFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
fun onItemClick(item: Item) {
sharedViewModel.selectItem(item)
}
}
// Fragment B
class DetailFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(...) {
lifecycleScope.launch {
sharedViewModel.selectedItem.collect { item ->
displayItem(item)
}
}
}
}
@Composable
fun UserScreen(
viewModel: UserViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsState()
when (val state = uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> UserContent(state.user)
is UiState.Error -> ErrorScreen(state.message)
}
}
flowchart TD
A["📱 Activity/Fragment Created"] --> B["🆕 ViewModel Created<br/>(nếu chưa tồn tại)"]
B --> C["🔄 Configuration Change<br/>(rotation)"]
C --> D["♻️ Activity/Fragment<br/>Destroyed & Recreated"]
D --> E["✅ ViewModel STILL EXISTS<br/>(same instance)"]
E --> F["🚪 Activity finished OR<br/>Fragment detached permanently"]
F --> G["🧹 ViewModel.onCleared()"]
G --> H["❌ ViewModel Destroyed"]
style B fill:#e8f5e9,stroke:#4caf50
style E fill:#e3f2fd,stroke:#2196f3
style G fill:#fff3e0,stroke:#ff9800
style H fill:#ffebee,stroke:#f44336

Cleanup resources khi ViewModel destroyed:

class MyViewModel : ViewModel() {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Main + job)
override fun onCleared() {
super.onCleared()
job.cancel() // Cancel all coroutines
// Cleanup other resources
}
}

Note: Với viewModelScope, không cần manual cleanup vì nó tự cancel.

// ✅ Good: Expose immutable state
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// ✅ Good: Use viewModelScope
viewModelScope.launch {
val data = repository.getData()
}
// ❌ Bad: Hold Context/View reference
class BadViewModel(private val context: Context) : ViewModel()
// ❌ Bad: Expose MutableStateFlow
val uiState = MutableStateFlow(UiState()) // Can be modified from outside
// ❌ Bad: Business logic in ViewModel
// Move to Repository or UseCase

Feature Description
Lifecycle Survives config changes
Scope viewModelScope
State StateFlow / LiveData
SavedState SavedStateHandle
Sharing activityViewModels()
Cleanup onCleared()