[Android]MVVM và một vài trick cho những người mới bắt đầu

[Android]MVVM và một vài trick cho những người mới bắt đầu

Kiến trúc MVVM là gì ?

Các ae theo đuổi bộ môn mobile này cũng gặp từ khoá MVVM này nhiều rồi nhỉ, MVVM là viết tắt của Model-View-ViewModel :D. Trong bài viết lần này mình sẽ tập trung vào View và ViewModel cho ae mới tiếp cận dễ hiểu hơn nhé.

Dựa theo kinh thư của Google thì:

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

View model là một class được thiết kế để lưu trữ và quản lý những công việc/ dữ liệu liên quan đến UI  theo vòng đời của chúng một cách rõ ràng nhất...

Bản chất của ViewModel cũng như kiến trúc MVVM là tách toàn bộ business logic ra khỏi ViewController(fragment/activity) và thực hiện những logic đó trong ViewModel.
Để dễ hiểu hơn về việc thực hiện logic này, mình sẽ có 1 ví dụ:

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        // Dùng bất đồng bộ để load user.
    }
}
ViewModel

Logic của việc fetch một list các user được thực hiện trong viewmodel và tiếp theo dưới đây, chúng ta sẽ theo dõi kết quả của việc fetch user đó và hiển thị lên UI.

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        val model: MyViewModel by viewModels()
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}
Fetch User từ ViewController và lắng nghe kết quả.

ViewBinding

ViewBinding cho phép người dùng theo tác dễ dàng hơn với lớp View(xml...). Khi thuộc tính viewbinding được enable trong build.gradle mỗi khi người dùng build project, file Binding sẽ được tạo tự động đối với những file xml.
Quan trong hơn cả đó là khi sử dụng viewbinding thì sẽ không còn phải khai báo findViewbyId nữa.

Cách enable:

android {
    ...
    buildFeatures {
        viewBinding = true
    }
}
Enable viewBinding trong build.gradle (app module level) 

Vậy là đã xong bước setup, sau đây sẽ là cách sử dụng chúng:

<LinearLayout ... >
    <TextView android:id="@+id/name" />
    <ImageView android:cropToPadding="true" />
    <Button android:id="@+id/button"
        android:background="@drawable/rounded_button" />
</LinearLayout>
result_profile.xml

File xml trên sẽ được tạo tự động thành ResultProfileBinding.
Sau đó ở fragment chúng ta có thể sử dụng binding trên như sau:

private lateinit var binding: ResultProfileBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ResultProfileBinding.inflate(layoutInflater)
    val view = binding.root
    setContentView(view)
}
Sử dụng binding trong fragment

Chúng ta sẽ không cần phải khai báo findviewbyid nữa mà sẽ gọi trực tiếp binding luôn:

binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }

DataBinding

Thư viện DataBinding cho phép người dùng thao tác gán dữ liệu vào UI trực tiếp trong file xml (không cần thông qua code thủ công).

Mình có một ví dụ nho nhỏ ở đây:

findViewById<TextView>(R.id.sample_text).apply {
    text = viewModel.userName
}
Cách gán dữ liệu truyền thống

Sử dụng data binding:

<TextView
    android:text="@{viewmodel.userName}" />
Cách gán dữ liệu sử dụng data binding

Trong build.gradle(app module level) chúng ta enable databinding bằng cách thêm vào đoạn code sau:

android {
    ...
    buildFeatures {
        viewBinding = true
    }
}
Enable data binding

Sử dụng với xml và viewmodel:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="viewmodel"
            type="com.myapp.data.ViewModel" />
    </data>
    <ConstraintLayout... /> <!-- UI layout's root element -->
</layout>
Data binding với viewmodel

File xml của chúng ta sẽ được bọc bởi thẻ <layout>. Và những giá trị hiển thị lên UI (text của textview, màu của button, ....) sẽ được gán trực tiếp tại file XML đó luôn. Như vậy sẽ làm file .kt cũng như là .java của người dùng gọn đi đáng kể.

Binding Adapter

Cũng tương tự như DataBinding, binding adapter được sử dụng để tạo ra những thuộc tính có thể sử dụng luôn trong xml thông qua những hàm extension.

Ví dụ:

Đầu tiên chúng ta cần khai báo với annotation @BindingAdapter và bên trong ngoặc là tên của thuộc tính sử dụng hàm extension này:

@BindingAdapter("srcRes")
fun ImageView.setSrcRes(drawableResId: Int) {
    setImageResource(drawableResId)
}
Hàm extension để load ảnh cho image view

Sau đó có thể sử dụng ở xml như sau:

<ImageView
            android:id="@+id/imageAvatar"
            srcRes="@{viewModel.imageAvatar}"
            android:layout_width="60dp"
            android:layout_height="60dp"/>
BindingAdapter

Ok vậy là đã xong vài trick cơ bản cho ae. Chúc ae có những phút giây vui vẻ, ko rage :D

Ref

ViewModel: Link
DataBinding: Link
BindingAdapter: Link