top of page
Tìm kiếm
Ảnh của tác giảthanh pham

Giới thiệu về mô hình MVVM trên Android

Đã cập nhật: 29 thg 8, 2021

Ở 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:


1.365 lượt xem0 bình luận

Bài đăng gần đây

Xem tất cả

Comments


bottom of page