Trang chủHướng dẫnGiới thiệu mô hình MVVM trong Android với LiveData và Data Binding
Android

Giới thiệu mô hình MVVM trong Android với LiveData và Data Binding

CyStack blog 5 phút để đọc
CyStack blog14/09/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 5 minutes

Chúng ta đã triển khai mô hình MVVM sử dụng Data Binding và đã tìm hiểu riêng về LiveData cũng như Data Binding trong các bài hướng dẫn trước.

Hôm nay, chúng ta sẽ kết hợp LiveData với Data Binding trong ứng dụng Android sử dụng mô hình MVVM. Qua đó, chúng ta sẽ thấy cách LiveData hỗ trợ cập nhật giao diện người dùng từ ViewModel trở nên dễ dàng hơn như thế nào.

mô hình MVVM trong Android

MVVM LiveData Data Binding

Trước đây, chúng ta đã dùng Data Binding để cập nhật View từ ViewModel. LiveData là một lớp tiện ích hoạt động như một vùng chứa dữ liệu cần truyền đi. Điểm nổi bật nhất của LiveData là khả năng nhận biết vòng đời (lifecycle-aware), nghĩa là nếu ứng dụng đang ở chế độ nền, giao diện sẽ không cố gắng cập nhật, từ đó tránh được nhiều lỗi thường gặp khi chạy ứng dụng.

Trong hướng dẫn này, chúng ta sẽ sử dụng MutableLiveData, vì lớp này cung cấp các phương thức công khai setValue()getValue().

Chúng ta sẽ tạo một ứng dụng đăng nhập đơn giản nhằm áp dụng các khái niệm vừa trình bày. Trước tiên, ta sẽ kết hợp LiveData với cơ chế Data Binding hai chiều, sau đó tiến hành refactor phần đang sử dụng Observable trong Data Binding để chuyển hoàn toàn sang LiveData.

Bắt đầu

Thêm các phụ thuộc sau vào tập tin build.gradle của ứng dụng:

android {
    ...

    dataBinding {
        enabled = true
    }
    ...
}

dependencies {
    ...
    implementation 'android.arch.lifecycle:extensions:1.1.1'
    implementation 'com.android.support:design:28.0.0-beta01'
    ...
}

Cấu trúc dự án

mô hình MVVM

Tập tin LoginViewModelOld sẽ chứa đoạn mã cũ, trong khi LoginViewModel sẽ chứa đoạn mã đã được refactor.

Model

Chúng ta định nghĩa lớp Model trong tập tin User.java như sau:

package com.journaldev.androidmvvmdatabindinglivedata;

import android.util.Patterns;

public class User {

    private String mEmail;
    private String mPassword;

    public User(String email, String password) {
        mEmail = email;
        mPassword = password;
    }

    public String getEmail() {
        if (mEmail == null) {
            return "";
        }
        return mEmail;
    }

    public String getPassword() {
        if (mPassword == null) {
            return "";
        }
        return mPassword;
    }

    public boolean isEmailValid() {
        return Patterns.EMAIL_ADDRESS.matcher(getEmail()).matches();
    }

    public boolean isPasswordLengthGreaterThan5() {
        return getPassword().length() > 5;
    }
}

Layout

Mã giao diện cho tập tin activity_main.xml như sau:

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

    <data>

        <variable
            name="loginViewModel"
            type="com.journaldev.androidmvvmdatabindinglivedata.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">

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:error="@{loginViewModel.errorEmail}"
                app:errorEnabled="true">

                <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="@={loginViewModel.email}" />

            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:error="@{loginViewModel.errorPassword}"
                app:errorEnabled="true">

                <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="@={loginViewModel.password}" />

            </android.support.design.widget.TextInputLayout>

            <Button
                android:id="@+id/button"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:onClick="@{()-> loginViewModel.onLoginClicked()}"
                android:text="LOGIN" />

            <ProgressBar
                android:id="@+id/progressBar"
                style="?android:attr/progressBarStyleLarge"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="8dp"
                android:visibility="@{loginViewModel.busy}" />

        </LinearLayout>

    </ScrollView>

</layout>

Thanh ProgressBar sẽ được hiển thị để mô phỏng tính năng đăng nhập.

ViewModel

Mã nguồn của lớp LoginViewModel.java được trình bày như sau:

package com.journaldev.androidmvvmdatabindinglivedata;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.databinding.ObservableField;
import android.os.Handler;
import android.support.annotation.NonNull;

public class LoginViewModel extends BaseObservable {
    private String email;
    private String password;
    private int busy = 8;
    public final ObservableField<String> errorPassword = new ObservableField<>();
    public final ObservableField<String> errorEmail = new ObservableField<>();

    public LoginViewModel() {
    }

    private MutableLiveData<User> userMutableLiveData;

    LiveData<User> getUser() {
        if (userMutableLiveData == null) {
            userMutableLiveData = new MutableLiveData<>();
        }

        return userMutableLiveData;
    }

    @Bindable
    @NonNull
    public String getEmail() {
        return this.email;
    }

    public void setEmail(@NonNull String email) {
        this.email = email;
        notifyPropertyChanged(BR.email);
    }

    @Bindable
    @NonNull
    public String getPassword() {
        return this.password;
    }

    public void setPassword(@NonNull String password) {
        this.password = password;
        notifyPropertyChanged(BR.password);
    }

    @Bindable
    public int getBusy() {
        return this.busy;
    }

    public void setBusy(int busy) {
        this.busy = busy;
        notifyPropertyChanged(BR.busy);
    }

    public void onLoginClicked() {

        setBusy(0); // View.VISIBLE
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

                User user = new User(getEmail(), getPassword());

                if (!user.isEmailValid()) {
                    errorEmail.set("Enter a valid email address");
                } else {
                    errorEmail.set(null);
                }

                if (!user.isPasswordLengthGreaterThan5()) {
                    errorPassword.set("Password Length should be greater than 5");
                } else {
                    errorPassword.set(null);
                }

                userMutableLiveData.setValue(user);
                setBusy(8); // View.GONE

            }
        }, 5000);
    }
}

ObservableField là một lớp bao bọc hỗ trợ khả năng quan sát các thay đổi của trường dữ liệu. Trong ví dụ trên, đối tượng User được quản lý thông qua LiveData, cho phép theo dõi MainActivity mỗi khi đối tượng User thay đổi để kích hoạt các hành vi tương ứng.

Khi người dùng nhấn nút, ProgressBar sẽ được hiển thị (View.VISIBLE = 0, View.GONE = 8). Sau khoảng trễ 5 giây, hệ thống tiến hành kiểm tra giá trị của email và mật khẩu, đồng thời cập nhật các thuộc tính liên kết (bindable) trong TextInputLayout.

Lưu ý, ObservableField không nhận biết vòng đời.

Lớp MainActivity.java được trình bày như sau:

package com.journaldev.androidmvvmdatabindinglivedata;

import android.arch.lifecycle.Observer;
import android.databinding.DataBindingUtil;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import com.journaldev.androidmvvmdatabindinglivedata.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        LoginViewModel loginViewModel = new LoginViewModel();
        binding.setLoginViewModel(loginViewModel);

        loginViewModel.getUser().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                if (user.getEmail().length() > 0 || user.getPassword().length() > 0)
                    Toast.makeText(getApplicationContext(), "email : " + user.getEmail() + " password " + user.getPassword(), Toast.LENGTH_SHORT).show();
            }
        });

    }
}

Trong đoạn mã trên, phương thức observe theo dõi mọi thay đổi của đối tượng User được chứa trong MutableLiveData. Khi có thay đổi, một thông báo Toast sẽ được hiển thị kèm theo tên đăng nhập và mật khẩu. Giờ đây, chúng ta sẽ thay thế hoàn toàn ObservableField bằng LiveData.

Refactor từ ObservableField sang LiveData

Mã nguồn mới cho lớp LoginViewModel.java như sau:

package com.journaldev.androidmvvmdatabindinglivedata;

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import android.os.Handler;

public class LoginViewModel extends ViewModel {

    public MutableLiveData<String> errorPassword = new MutableLiveData<>();
    public MutableLiveData<String> errorEmail = new MutableLiveData<>();

    public MutableLiveData<String> email = new MutableLiveData<>();
    public MutableLiveData<String> password = new MutableLiveData<>();
    public MutableLiveData<Integer> busy;

    public MutableLiveData<Integer> getBusy() {

        if (busy == null) {
            busy = new MutableLiveData<>();
            busy.setValue(8);
        }

        return busy;
    }

    public LoginViewModel() {
    }

    private MutableLiveData<User> userMutableLiveData;

    LiveData<User> getUser() {
        if (userMutableLiveData == null) {
            userMutableLiveData = new MutableLiveData<>();
        }

        return userMutableLiveData;
    }

    public void onLoginClicked() {

        getBusy().setValue(0); // View.VISIBLE
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

                User user = new User(email.getValue(), password.getValue());

                if (!user.isEmailValid()) {
                    errorEmail.setValue("Enter a valid email address");
                } else {
                    errorEmail.setValue(null);
                }

                if (!user.isPasswordLengthGreaterThan5()) {
                    errorPassword.setValue("Password Length should be greater than 5");
                } else {
                    errorPassword.setValue(null);
                }

                userMutableLiveData.setValue(user);
                busy.setValue(8); // View.GONE

            }
        }, 3000);
    }
}

Lớp trên hiện tại kế thừa từ ViewModel do không còn cần sử dụng BaseObservable. Các ObservableField đã được thay thế bằng MutableLiveData. Mọi thay đổi của MutableLiveData sẽ được tự động cập nhật trong giao diện nhờ vào Data Binding.

Lớp MainActivity.java sau khi cập nhật như sau:

package com.journaldev.androidmvvmdatabindinglivedata;

import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.databinding.DataBindingUtil;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import com.journaldev.androidmvvmdatabindinglivedata.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        LoginViewModel loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
        binding.setLoginViewModel(loginViewModel);
        binding.setLifecycleOwner(this);

        loginViewModel.getUser().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                if (user.getEmail().length() > 0 || user.getPassword().length() > 0)
                    Toast.makeText(getApplicationContext(), "email : " + user.getEmail() + " password " + user.getPassword(), Toast.LENGTH_SHORT).show();
            }
        });

    }
}

ViewModelProviders.of cũng có thể được sử dụng để khởi tạo một thể hiện ViewModel như đã làm ở trên. Phương thức này chỉ khởi tạo ViewModel một lần duy nhất, các lần gọi tiếp theo sẽ tái sử dụng thể hiện đã có. LifecycleOwner là một interface mà Activity có thể liên kết.

Kết quả đầu ra của ứng dụng khi chạy được trình bày dưới đây:

mô hình MVVM

Kết quả hiển thị của ứng dụng trong thực tế cho thấy thông báo Toast sẽ không xuất hiện nếu người dùng rời khỏi ứng dụng, bởi vì LiveData nhận biết được vòng đời. Khi mở lại ứng dụng, thông báo sẽ hiển thị. Đây cũng là phần kết của bài hướng dẫn này.

Bạn có thể tải mã nguồn dự án từ liên kết bên dưới:

AndroidMVVMDataBindingLiveData

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