Trang chủHướng dẫnCách triển khai mô hình MVVM trong Android [Ví dụ minh họa]
Chuyên gia

Cách triển khai mô hình MVVM trong Android [Ví dụ minh họa]

CyStack blog 6 phút để đọc
CyStack blog12/06/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 6 minutes

Trong bài hướng dẫn này, chúng ta sẽ thảo luận và triển khai mô hình MVVM trong Android.

Tại sao chúng ta cần các pattern dạng này? Việc dồn tất cả logic vào một Activity hay Fragment duy nhất sẽ gây khó khăn cho việc kiểm thử (testing) và tái cấu trúc (refactoring) code. Do đó, chúng ta nên tách biệt code và áp dụng một kiến trúc sạch.

Mô hình MVVM trong Android

Android MVVM

MVVM là viết tắt của Model, View, ViewModel.

  • Model: Chứa dữ liệu của ứng dụng. Model không giao tiếp trực tiếp với View. Thông thường, dữ liệu từ Model nên được cung cấp cho ViewModel thông qua các Observable (đối tượng quan sát được).
  • View: Đại diện cho phần UI của ứng dụng, không chứa bất kỳ logic nghiệp vụ (application logic) nào. Công việc của View là theo dõi ViewModel.
  • ViewModel: Đóng vai trò trung gian giữa Model và View. ViewModel chịu trách nhiệm chuyển đổi dữ liệu từ Model, cung cấp các luồng dữ liệu (data stream) cho View. Nó cũng sử dụng các hook (móc nối) hoặc callback (hàm gọi lại) để cập nhật View. ViewModel sẽ yêu cầu dữ liệu từ Model.

Sơ đồ dưới đây minh họa luồng hoạt động cốt lõi của MVVM.

Sơ đồ minh họa mẫu thiết kế MVVM trong Android

Vậy MVVM khác MVP như thế nào?

  • ViewModel thay thế Presenter ở layer (tầng) giữa.
  • Presenter giữ tham chiếu đến View, còn ViewModel thì không.
  • Presenter cập nhật View theo cách truyền thống (gọi phương thức).
  • ViewModel gửi đi các luồng dữ liệu.
  • Presenter và View có mối quan hệ 1-1.
  • View và ViewModel có mối quan hệ 1-nhiều (một ViewModel có thể tương tác với nhiều View).
  • ViewModel không biết View nào đang lắng nghe nó.

Có hai cách để triển khai MVVM trong Android:

  • Data binding (liên kết dữ liệu)
  • RXJava

Trong bài hướng dẫn này, chúng ta sẽ chỉ sử dụng data binding. Thư viện Data Binding được Google giới thiệu nhằm mục đích liên kết dữ liệu trực tiếp trong file layout XML.

Chúng ta sẽ tạo một ứng dụng ví dụ đơn giản với chức năng đăng nhập và yêu cầu người dùng nhập thông tin của họ. Qua đó, ta sẽ thấy cách ViewModel thông báo cho View hiển thị một thông báo toast mà không cần giữ tham chiếu đến View.

Làm thế nào để thông báo đến một class mà không cần giữ tham chiếu của nó? Ta có ba cách để thực hiện điều này:

  • Sử dụng liên kết dữ liệu hai chiều
  • Sử dụng dữ liệu sống (live data)
  • Sử dụng RxJava

Liên kết dữ liệu hai chiều

Liên kết dữ liệu hai chiều (two-way data binding) là một kỹ thuật liên kết các đối tượng với layout XML, cho phép cả đối tượng và layout đều có thể gửi dữ liệu cho nhau. Trong trường hợp của chúng ta, ViewModel có thể gửi dữ liệu đến layout và đồng thời theo dõi các thay đổi từ layout.

Để làm được điều này, chúng ta cần một BindingAdapter và một thuộc tính tùy chỉnh được định nghĩa trong XML. BindingAdapter sẽ lắng nghe những thay đổi của thuộc tính này. Chúng ta sẽ tìm hiểu kỹ hơn về liên kết dữ liệu hai chiều qua ví dụ cụ thể dưới đây.

Cấu trúc dự án ví dụ Android MVVM

Sơ đồ project ví dụ

Thêm thư viện Data Binding

Thêm đoạn code sau vào file build.gradle của ứng dụng:

android {

    dataBinding {
        enabled = true
    }
}

Thao tác này sẽ kích hoạt Data Binding trong ứng dụng của bạn.

Thêm các dependency

Thêm các dependency (phụ thuộc) sau vào file build.gradle của bạn:

implementation 'android.arch.lifecycle:extensions:1.1.0'

Model

Model sẽ chứa email và mật khẩu của người dùng. Cụ thể, class User.java dưới đây sẽ làm điều đó:

package com.journaldev.androidmvvmbasics.model;

public class User {
    private String email;
    private String password;

    public User(String email, String password) {
        this.email = email;
        this.password = password;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getEmail() {
        return email;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

}

Liên kết dữ liệu hai chiều cho phép chúng ta liên kết các đối tượng trong layout XML theo cách mà cả đối tượng và layout đều có thể gửi dữ liệu cho nhau. Cú pháp của nó là @={variable}.

Layout

Code cho activity_main.xml sẽ giống như sau:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="<https://schemas.android.com/apk/res/android>"
    xmlns:bind="<https://schemas.android.com/tools>">

    <data>

        <variable
            name="viewModel"
            type="com.journaldev.androidmvvmbasics.viewmodels.LoginViewModel" />
    </data>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="8dp"
            android:orientation="vertical">

            <EditText
                android:id="@+id/inEmail"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Email"
                android:inputType="textEmailAddress"
                android:padding="8dp"
                android:text="@={viewModel.userEmail}" />

            <EditText
                android:id="@+id/inPassword"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Password"
                android:inputType="textPassword"
                android:padding="8dp"
                android:text="@={viewModel.userPassword}" />

            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:onClick="@{()-> viewModel.onLoginClicked()}"
                android:text="LOGIN"
                bind:toastMessage="@{viewModel.toastMessage}" />

        </LinearLayout>

    </ScrollView>

</layout>

Data Binding yêu cầu chúng ta đặt tag <layout> ở đầu file. Trong ví dụ này, ViewModel sẽ liên kết dữ liệu với View. Lệnh gọi ()-> viewModel.onLoginClicked() sẽ kích hoạt một lambda listener cho sự kiện click của Button, vốn được định nghĩa trong ViewModel. EditText sẽ cập nhật các giá trị trong Model (thông qua ViewModel). Thuộc tính tùy chỉnh bind:toastMessage="@{viewModel.toastMessage}” được sử dụng cho liên kết dữ liệu hai chiều. Dựa trên những thay đổi của toastMessage trong ViewModel, BindingAdapter tương ứng trong View sẽ được kích hoạt.

ViewModel

Đoạn code cho LoginViewModel.java:

package com.journaldev.androidmvvmbasics.viewmodels;

import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.text.TextUtils;
import android.util.Patterns;

import com.android.databinding.library.baseAdapters.BR;
import com.journaldev.androidmvvmbasics.model.User;

public class LoginViewModel extends BaseObservable {
    private User user;

    private String successMessage = "Login was successful";
    private String errorMessage = "Email or Password not valid";

    @Bindable
    private String toastMessage = null;

    public String getToastMessage() {
        return toastMessage;
    }

    private void setToastMessage(String toastMessage) {

        this.toastMessage = toastMessage;
        notifyPropertyChanged(BR.toastMessage);
    }

    public void setUserEmail(String email) {
        user.setEmail(email);
        notifyPropertyChanged(BR.userEmail);
    }

    @Bindable
    public String getUserEmail() {
        return user.getEmail();
    }

    @Bindable
    public String getUserPassword() {
        return user.getPassword();
    }

    public void setUserPassword(String password) {
        user.setPassword(password);
        notifyPropertyChanged(BR.userPassword);
    }

    public LoginViewModel() {
        user = new User("","");
    }

    public void onLoginClicked() {
        if (isInputDataValid())
            setToastMessage(successMessage);
        else
            setToastMessage(errorMessage);
    }

    public boolean isInputDataValid() {
        return !TextUtils.isEmpty(getUserEmail()) && Patterns.EMAIL_ADDRESS.matcher(getUserEmail()).matches() && getUserPassword().length() > 5;
    }
}

Các phương thức được gọi từ layout XML được triển khai trong ViewModel với cùng signature (khai báo hàm). Nếu phương thức tương ứng trong XML không tồn tại, chúng ta cần thay đổi thuộc tính thành app:.

Class trên có thể kế thừa từ ViewModel, nhưng ở đây chúng ta sử dụng BaseObservable vì nó giúp chuyển đổi dữ liệu thành các luồng (stream) và tự động thông báo khi một thuộc tính toastMessage thay đổi.

Chúng ta cần định nghĩa getter và setter (phương thức truy xuất và thay đổi) cho thuộc tính tùy chỉnh toastMessage đã khai báo trong XML. Bên trong setter của toastMessage, chúng ta sẽ thông báo cho observer (chính là View trong ứng dụng) rằng dữ liệu đã thay đổi. View (Activity của chúng ta) sau đó có thể xác định hành động xử lý cần thiết tương ứng.

Class BR được Data Binding tự động sinh ra khi bạn rebuild lại project.

Hãy xem qua code cho class chứa BindingAdapter:

package com.journaldev.androidmvvmbasics.views;

import android.databinding.BindingAdapter;
import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import com.journaldev.androidmvvmbasics.R;
import com.journaldev.androidmvvmbasics.databinding.ActivityMainBinding;
import com.journaldev.androidmvvmbasics.viewmodels.LoginViewModel;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        activityMainBinding.setViewModel(new LoginViewModel());
        activityMainBinding.executePendingBindings();

    }

    @BindingAdapter({"toastMessage"})
    public static void runMe(View view, String message) {
        if (message != null)
            Toast.makeText(view.getContext(), message, Toast.LENGTH_SHORT).show();
    }
}

Nhờ có Data Binding, class ActivityMainBinding được tự động sinh ra từ layout. Phương thức @BindingAdapter sẽ được kích hoạt bất cứ khi nào giá trị của thuộc tính toastMessage được định nghĩa Button thay đổi. Nó phải khớp với tên đã định nghĩa trong XML và được ViewModel sử dụng.

Như vậy, trong ứng dụng này, ViewModel cập nhật Model bằng cách lắng nghe các thay đổi từ View. Ngược lại, Model cũng có thể cập nhật View thông qua ViewModel với notifyPropertyChanged.

Tương tác thực tế của ứng dụng sẽ trông giống như hình minh họa dưới đây.

App ví dụ sử dụng thiết kế MVVM trong Android

Tổng kết

Bạn có thể tải mã nguồn ví dụ ở trên từ link này. Đừng ngần ngại thử nghiệm những kiến thức trên vào những project của riêng mình để trở nên thành thục hơn trong việc sử dụng kiến trúc MVVM trong Android nhé.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất