Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Ứng dụng nhỏ viết mọi thứ trong một màn hình vẫn chạy được. Nhưng khi tính năng nhiều lên, mã trộn lẫn giao diện, gọi mạng và xử lý dữ liệu trở nên rối và khó kiểm thử. MVVM là một cách tổ chức mã phổ biến để giải quyết vấn đề đó.
MVVM gồm ba phần:
View quan sát trạng thái do ViewModel công bố và tự cập nhật khi nó đổi. Khi người dùng thao tác, View chuyển sự kiện cho ViewModel xử lý.
Trạng thái màn hình được mô tả bằng một lớp dữ liệu:
data class UsersUiState(
val loading: Boolean = false,
val users: List<User> = emptyList(),
val error: String? = null
)
class UsersViewModel(private val repo: UserRepository) : ViewModel() {
private val _state = MutableStateFlow(UsersUiState())
val state: StateFlow<UsersUiState> = _state
init { load() }
fun load() {
viewModelScope.launch {
_state.value = UsersUiState(loading = true)
try {
_state.value = UsersUiState(users = repo.getUsers())
} catch (e: Exception) {
_state.value = UsersUiState(error = "Không tải được dữ liệu")
}
}
}
}
View (Compose) chỉ vẽ theo trạng thái:
@Composable
fun UsersScreen(vm: UsersViewModel) {
val state by vm.state.collectAsState()
when {
state.loading -> CircularProgressIndicator()
state.error != null -> Text(state.error!!)
else -> LazyColumn {
items(state.users) { Text(it.name) }
}
}
}
Trong Flutter, vai trò ViewModel thường do ChangeNotifier, Cubit hay Notifier của Riverpod đảm nhận. Trong SwiftUI, đó là lớp ObservableObject (hoặc macro Observable) cùng thuộc tính được quan sát. React Native dùng custom hook hoặc kho trạng thái để chứa logic. Tên gọi khác nhưng nguyên lý giống nhau: giao diện chỉ phản ánh trạng thái.
Thêm một lớp Repository giữa ViewModel và nguồn dữ liệu giúp ViewModel không cần biết dữ liệu đến từ mạng hay cơ sở dữ liệu cục bộ:
interface UserRepository {
suspend fun getUsers(): List<User>
}
class UserRepositoryImpl(
private val api: UserApi,
private val dao: UserDao
) : UserRepository {
override suspend fun getUsers(): List<User> {
return try {
val remote = api.fetchUsers()
dao.saveAll(remote)
remote
} catch (e: IOException) {
dao.getAll() // dự phòng khi mất mạng
}
}
}
Có những kiến trúc khác như MVI, Clean Architecture hay Redux-like với luồng dữ liệu một chiều chặt chẽ hơn. MVVM là điểm khởi đầu hợp lý, và nhiều kiến trúc khác xây dựng trên cùng ý tưởng tách giao diện khỏi logic.
MVVM không phải phép màu, nhưng giúp bạn viết ứng dụng có cấu trúc, dễ kiểm thử và dễ mở rộng. Hãy áp dụng ở mức vừa đủ, bắt đầu từ việc đưa trạng thái và logic ra khỏi màn hình.
Lợi ích lớn của MVVM là kiểm thử dễ. Với ViewModel ở trên, bạn truyền một repository giả trả về dữ liệu cố định hoặc ném lỗi, gọi hàm load, rồi kiểm tra giá trị state có đúng như mong đợi không. Bạn không cần thiết bị hay giao diện nào. Hãy kiểm thử ba tình huống: tải thành công, danh sách rỗng và lỗi mạng. Những bài kiểm thử này chạy trong mili giây và bảo vệ bạn khi sau này thay đổi logic.
Để học kiến trúc ứng dụng qua dự án thực tế, bạn có thể tham khảo các khóa học trên codebem.com.
Nguyễn Đình Cường
0 comment