ViewModel trong Android
1. Giới thiệu
ViewModel là class giữ và quản lý UI-related data theo cách lifecycle-aware.
2. Đặc điểm
- 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
3. Tạo ViewModel cơ bản
class CounterViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value++
}
fun decrement() {
_count.value--
}
}4. Sử dụng trong Activity/Fragment
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
)
}
}
}5. ViewModel với parameters
ViewModelFactory
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)
}Với Hilt (Recommended)
@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()6. SavedStateHandle
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", "")
}7. Shared ViewModel
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)
}
}
}
}8. Compose với ViewModel
@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)
}
}9. ViewModel Lifecycle
Activity/Fragment Created
↓
ViewModel Created (nếu chưa tồn tại)
↓
Configuration Change (rotation)
↓
Activity/Fragment Destroyed & Recreated
↓
ViewModel STILL EXISTS (same instance)
↓
Activity finished OR Fragment detached permanently
↓
ViewModel.onCleared() called
↓
ViewModel Destroyed10. onCleared
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.
11. Best Practices
// ✅ 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📝 Tóm tắt
| Feature | Description |
|---|---|
| Lifecycle | Survives config changes |
| Scope | viewModelScope |
| State | StateFlow / LiveData |
| SavedState | SavedStateHandle |
| Sharing | activityViewModels() |
| Cleanup | onCleared() |
Last updated on