Ở bài viết trước mình đã đề cập tới mọi người mô hình MVP, đây là mô hình đang được áp dụng khá nhiều trong quá trình phát triển ứng dụng Android. Nhưng MVP vẫn có một vài hạn chế là Presenter sẽ dần lớn lên. Khi sự phức tạp hay độ lớn của ứng dụng tăng lên dẫn đến việc duy trì và xử lý logic trở nên phức tạp. Lập trình viên sẽ rất khó để kiểm soát và chia nhỏ code khi Presenter đã quá lớn. Thêm nữa Bởi vì Presenter bị ràng buộc chặt chẽ với View đây là quy tắc ràng buộc cứng nhắc. Presenter giữ tham chiếu đến View và View cũng giữ tham chiếu đến Presenter. Mối quan hệ 1:1 và đó là vấn đề lớn nhất, dẫn đến việc Unit Test trở khó nên hơi khăn. Đây là 2 lý do khá lớn mà MVP vẫn chưa thực sự giải quyết triệt để.
Chính vì những hạn chế trên của Presenter, MVVM được ra đời để cải thiện một số điểm còn đang thiếu sót của MVP. Đây cũng chính là mô hình mà mình sẽ giới thiệu đến mọi người hôm nay.
1. Lịch sử hình thành
MVVM là một biến thể của mẫu thiết kế mô hình trình bày của Martin Fowler. Nó được phát minh bởi các kiến trúc sư Microsoft, Ken Cooper và Ted Peters đặc biệt để đơn giản hóa việc lập trình theo hướng sự kiện của giao diện người dùng. Mẫu này được kết hợp vào Windows Presentation Foundation (WPF) ( hệ thống đồ họa .NET của Microsoft ) và Silverlight (dẫn xuất ứng dụng Internet của WPF). John Gossman, một trong những kiến trúc sư WPF và Silverlight của Microsoft, đã công bố MVVM trên blog của mình vào năm 2005.
Từ đó đến nay MVVM càng khẳng định vị thế của mình là một mô hình phù hợp cũng như mạnh mẽ trong phát triển phần mềm. Trong Google I/O 2017 Google đã công bố 1 chuẩn về Architecture Components. Nó hỗ trợ rất mạnh mẽ mô hình MVVM như một lời gợi ý của Google về mô hình này.
Như chúng ta đã thảo luận, có nhiều loại kiến trúc cho các ứng dụng Android. Các đề xuất chính của Google hỗ trợ MVVM, tận dụng những thứ như LiveData và ViewModels để giải quyết hai vấn đề phổ biến nhất mà ứng dụng Android phải đối mặt: thay đổi vòng đời và xoay màn hình. Sự tách biệt hợp lý giữa logic và hành vi cho phép các ứng dụng linh hoạt và dễ bảo trì.
2. Tìm hiểu chi tiết
ViewModel
ViewModel là một abstraction của View. Nó sẽ lấy dữ liệu từ tầng Model, xử lý UI logic sau đó hiển thị data có liên quan tới view. ViewModel sẽ không có bất kỳ behavior nào để tương tác với View. Như vậy để nhận biết khi nào cần hiển thị dữ liệu, View sẽ đăng ký lắng nghe sự kiện từ ViewModel.
ViewModels sẽ sử dụng các model nếu cần định nghĩa dữ liệu. Sự liên kết giữa View - ViewModel giúp chúng gửi và nhận dữ liệu, để hiểu rõ ta cần tìm hiểu các khái niệm về Binding, DataContext, Behaviors SDK. Nhờ đó ta tách code-behind của View và đưa xuống ViewModel.
View
Thành phần giao diện của ứng dụng. View là thành phần duy nhất mà người dùng có thể tương tác được trong chương trình, nó chính là thành phần mô tả dữ liệu. Trong lập trình android, View là một activity, fragment, hay một custom view...
Quan hệ giữa ViewModel và View là 1-n, nghĩa là nhiều View có thể liên kết với 1 ViewModel
Model
Model là các đối tượng giúp truy xuất và thao tác trên dữ liệu. Model chứa phần data được lấy từ nhiều nguồn khác nhau, ví dụ như: từ remote hoặc từ data local.
Tuy nhiên cần phải lưu ý đó là model chỉ lưu giữ thông tin mà thôi, nó không quan tâm đến các hoạt động hay dịch vụ có thể thay đổi, điều khiển các thông tin đó. Ví dụ như nó không có trách nhiệm phải định dạng đoạn văn bản hiển thị như thế nào, hay làm sao để lấy một danh sách các item về từ remote server.
3. Demo
Mình sẽ cùng các bạn làm một demo cơ bản về xử lý màn Login theo mô hình MVP
Bước 1: Tạo Model
User.kt
classUser(
var username:String?,
var password:String?
)
Bước 2: Tạo ViewModel xử lý các logic của UI
username, password được lưu trữ trong viewModel để đảm bảo tồn tại ngay cả khi có sự thay đổi về config
LoginViewModel.kt
class LoginViewModel : ViewModel() {
var username = MutableLiveData<String>()
var password = MutableLiveData<String>()
val isLoginSuccess = MutableLiveData<Boolean>().apply {
value =false }
private fun handleLogin(user:User) {
// Các bạn có thể xử lý logic call API tại đây
isLoginSuccess.value = true
}
private fun validate(username:String, password:String):
Boolean {
var validated = true
if (username.isEmpty() || password.isEmpty()) {
validated =false
}
return validated
}
fun onLogin() {
if (!validate(
username.value.toString(),
password.value.toString())
)
return handleLogin(
User(username.value.toString(),
password.value.toString()))
}
}
Note
Đảm bảo rằng lưu trữ biến LiveData ở trong ViewModel, tránh lưu trữ ở Activities và Fragments vì thế sẽ giảm tải logic, kích thước file của View. Activity và Fragment chỉ đảm bảo nhiệm vụ hiển thị data chứ không lưu trữ data cũng như lưu trữ LiveData ở ViewModel đảm bảo LiveData tồn tại ngay cả khi có sự thay đổi về config.
Bước 3: Tạo layout
Bạn có thể thấy ở đây chúng ta có thể tương tác trực tiếp với ViewModel trong file xml
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variablename="viewModel"
type="com.demo.mvvm.viewmodel.LoginViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edtUserName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:ems="10"
android:inputType="textPersonName"
android:text="@={viewModel.username}"
app:layout_constraintBottom_toTopOf="@+id/edtPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<EditText
android:id="@+id/edtPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:ems="10"
android:inputType="textPassword"
android:text="@={viewModel.password}"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintTop_toBottomOf="@+id/edtUserName"
app:layout_constraintStart_toStartOf="parent"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.onLogin()}"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintTop_toBottomOf="@+id/edtPassword"
app:layout_constraintStart_toStartOf="parent"/>
</android.support.constraint.ConstraintLayout>
</layout>
Bước 4: Xử lý logic trong Activity
Sau khi đăng ký ViewModel chúng ta có thể lắng nghe sự kiện thay đổi của biến isLoginSuccess thông qua phương thức Observer
LoginActivity.kt
class LoginActivity : AppCompatActivity() {
private var viewModel:LoginViewModel=LoginViewModel()
overridefunonCreate(savedInstanceState:Bundle?) {
super.onCreate(savedInstanceState)
val activityMainBinding: ActivityLoginBinding =
DataBindingUtil.setContentView(this,
R.layout.activity_login)
activityMainBinding.apply {
viewModel = viewModel
executePendingBindings()
}
observeField()
}
private fun observeField() {
viewModel.isLoginSuccess.observe(this, Observer {
isLogin ->if (isLogin !=null) {
startMain(isLogin)
}
})
}
private fun startMain(isLogin:Boolean) =if (isLogin) {
startActivity(Intent(this, MainActivity::class.java))
} else {
Toast.makeText(this, "Loginfail!",Toast.LENGTH_LONG).show()
}
}
4. Kết luận
ViewModel là các class mô phỏng tương tác với logic/model layer và chỉ hiện trạng thái dữ liệu mà không quan tâm ai hoặc dữ liệu sẽ được tiêu thụ thế nào. Chỉ View giữ tham chiếu đến ViewModel và không có trường hợp ngược lại, điều này giải quyết vấn đề của Presenter và View. Một View có thể giữ tham chiếu nhiều ViewModel.
Về cơ bản chúng ta sẽ thấy MVVM khá nhiều điểm tương đồng với MVP.
MVVM đã kế thừa những ưu điểm vốn có của MVP, kết hợp với những lợi thế của data binding đem đến một pattern có khả năng phân chia các thành phần với từng chức năng riêng biệt, dễ dàng trong việc maintain, redesign. MVVM cũng đem lại khả năng test rất dễ dàng, giúp làm việc hiệu quả hơn cho lập trình viên.
Note
Khả năng duy trì khi view có thể gán cả biến và biểu thức, các logic không liên quan sẽ tăng dần theo thời gian, ảnh hưởng đến việc thêm code vào XML.
References:
Comments