+2

Tìm hiểu về SavedState cho ViewModel

Giới thiệu

Tại sự kiện Google I/O 2018, Google giới thiệu về Android Jetpack, một bộ công cụ, thành phần và hướng dẫn để tạo ứng dụng Android xuất sắc hơn. Nó bao gồm các thành phần như LiveData, ViewModel, Room Database, Work Manager, và nhiều thành phần khác. Trong bài viết này chúng ta sẽ nói về ViewModel.

ViewModel quản lý dữ liệu liên quan đến giao diện người dùng trong vòng đời của Activity/Fragment. Nó cho phép dữ liệu tồn tại qua các thay đổi cấu hình trong ứng dụng (như xoay màn hình).

ViewModel thường được dùng để:

  1. Chuẩn bị dữ liệu cho tầng View
  2. Xử lý cấu hình khu ứng dụng xoay màn hình.

PS: Lớp ViewModel không nên chứa tham chiếu đến View (Activity/Fragment).

Vòng đời của ViewModel

ViewModel và onSaveInstanceState()

Triển khai ViewModel,

class MainViewModel : ViewModel() {
    val blogs = MutableLiveData<List<Blog>>()
    fun getBlogs(): LiveData<List<Blog>> {
        return blogs
    }
    private fun loadBlogs() {
        blogs.value = //our list of blogs
    }
}

và để lấy dữ liệu trên View,

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MainViewModel instance created by the first activity.
        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)
        model.getBlogs().observe(this, Observer<List<Blog>>{ blogs ->
            // update UI
        })
    }
}

Trong View (Activitys/Fragments), chúng ta phải kích hoạt ViewModel bằng cách sử dụng,

       val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

và chúng ta có thể observe dữ liệu bằng,

model.getBlogs().observe(this, Observer<List<Blog>>{ blogs ->
            // update UI
        })

Đây là cách chúng ta sử dụng ViewModel trong View của mình.

Bây giờ, trong ViewModel, chúng ta chỉ có thể xử lý các thay đổi cấu hình trong View, nhưng nó không lưu trữ Trạng thái của UI hoặc View. Nhưng nó không xử lý Sự chết của quá trình được khởi tạo bởi hệ thống. Để xử lý điều này, chúng ta có thể sử dụng onSaveInstanceState() để khôi phục trạng thái chúng ta đã rời đi.

Sự chết của quá trình được khởi tạo bởi hệ thống có thể được định nghĩa như sau: Mỗi ứng dụng Android chạy trong process Linux riêng của nó. Process này được tạo ra cho ứng dụng khi một số mã của nó cần được thực thi và sẽ tiếp tục chạy cho đến khi không còn cần thiết nữa và hệ thống cần thu hồi bộ nhớ của nó để sử dụng cho các ứng dụng khác.

Nếu ứng dụng của bạn không ở trong trạng thái foreground, hệ thống có thể chấm dứt quá trình của bạn bất kỳ lúc nào để giải phóng bộ nhớ hệ thống để sử dụng cho các quá trình khác.

  • Phương thức onSaveInstanceState() và bundle có thể xử lý cả cấu hình và sự chết của quá trình được khởi tạo bởi hệ thống. Khi chúng ta mở lại ứng dụng và chuyển ứng dụng của chúng ta vào trạng thái foreground, chúng ta vẫn có thể thấy dữ liệu của mình được lưu trữ bằng onSaveInstanceState().
  • Nhưng nó chỉ có thể lưu trữ một lượng dữ liệu hạn chế và phụ thuộc rất nhiều vào tốc độ cũng như dung lượng lưu trữ vì việc Tuần tự hóa (Serialization) có thể tốn rất nhiều bộ nhớ để lưu trữ dữ liệu.
  • Quá trình tuần tự hóa xảy ra trên luồng chính nên trong khi thay đổi cấu hình, nó có thể chặn màn hình giao diện người dùng và cũng có thể khiến ứng dụng bị treo (ANR), tức là Ứng dụng không phản hồi.
  • Phương thức onSaveInstanceState() có thể lưu trữ một lượng dữ liệu tối thiểu và không phải là lượng lớn.

SavedState

Trong sự kiện Google I/O 2019, Google đã ra mắt SavedState cho ViewModel link

Saved State của ViewModel có thể được xem xét như một phương thức thay thế cho onSaveInstanceState(), vì nó cũng có thể lưu trữ dữ liệu. Bởi vì dữ liệu UI luôn được tham chiếu từ ViewModel của Architecture Components và không phải từ View (Activity/Fragment). Vì vậy, để sử dụng onSaveInstanceState(), chúng ta sẽ cần phải thực hiện một số code.

Vì vậy, với tư cách là một phần của Jetpack, Google đã ra mắt một thứ gọi là Saved State, giúp chúng ta lưu trữ và truy xuất dữ liệu từ trạng thái đã lưu sau khi nó trải qua System Initiated Process Death.

Triển khai

Để tích hợp Saved State trong dự án, thêm mã bên dưới vào build.gradle của project

implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha01'

cập nhật đoạn code bên dưới trong onCreate() của View (Activity/Fragment) của bạn,

val model = ViewModelProviders.of(this, SavedStateVMFactory(this)).get(MainViewModel::class.java)

thay cho,

val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

và trong ViewModel,

class MainViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

Ở đây, bạn có thể thấy chúng ta đã chuyển SavedStateHandle vào hàm tạo chính của ViewModel. Để có được SavedStateHandle, chúng ta truyền SavedStateVMFactory() vào ViewModelProviders.of như một Factory.

Factory là một Interface cho ViewModel biết cách tạo ViewModel.

Ở đây, SavedStateHandle là một bảng điều khiển nằm trong một cặp Key-Value, giúp bạn đọc và ghi đối tượng vào và từ trạng thái đã lưu. Dữ liệu vẫn sẽ được duy trì ngay cả khi ứng dụng trải qua sự chết của quá trình được khởi tạo bởi hệ thống.

  • SavedStateHandle giống như SharedPreferences trong Android, hoạt động dựa trên cặp Key-Value.

Ví dụ

  • Chúng ta sẽ xây dựng một ứng dụng với 3 thành phần giao diện người dùng (EditText, Button và TextView).
  • Khi người dùng nhập tên người dùng vào EditText và nhấp vào nút, ứng dụng sẽ hiển thị tên người dùng trong TextView.

Đây là những gì chúng ta sẽ xây dựng, Bây giờ MainViewModel sẽ trông như thế này,

class MainViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel(), BaseViewModel {

    override fun getUserName(): LiveData<String> {
        return savedStateHandle.getLiveData(Constants.USERNAME)
    }
    override fun saveUserName(username: String) {
        savedStateHandle.set(Constant.USERNAME, username)
    }
}
  • saveStateHandle.set("Key","Value") được sử dụng để lưu trữ dữ liệu
  • saveStateHandle.getLiveData("key") được sử dụng để trả về LiveData của kiểu dữ liệu String

BaseViewModel trông giống như sau và được triển khai bởi Lớp MainViewModel.

interface BaseViewModel {
    fun getUserName(): LiveData<String>
    fun saveUserName(username: String)
}

Và cuối cùng, tệp lớp MainActivity trông như sau,

class MainActivity : AppCompatActivity() {
    lateinit var mainViewModel: MainViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val factory = SavedStateVMFactory(this@MainActivity)
        mainViewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)
        setupActions()
    }

    private fun setupActions() {
        submitButton.setOnClickListener {
            mainViewModel.saveUserName(username_edittext.text.toString()
        }
        mainViewModel.getLiveDataUserName().observe(this, Observer {
            saved_textview.text= "LiveData Result: $it"
        })
    }
}

Bây giờ, để kiểm tra SavedState, bạn cần có Emulator/Device OS ít nhất Android Pie (+). Chúng ta phải tuân theo trạng thái sau để mô phỏng System Initiate Process Death,

  • Chúng ta phải đảm bảo rằng Ứng dụng hiện đang chạy trên Thiết bị Android bằng cách
adb shell ps -A | grep lifecycle
  • Nó sẽ hiển thị Output có tên vòng đời Ứng dụng của bạn như " com.android.app " trong terminal của bạn
adb shell am kill your-app-package-name
  • Để xác nhận, bạn nên chạy lại Bước 1 và bạn sẽ không thấy tên gói ứng dụng của bạn trong cửa sổ terminal.
  • Bây giờ hãy mở lại ứng dụng và bạn sẽ thấy đầu ra được lưu và hiển thị trong TextView của màn hình.

Một số thứ cần lưu ý

  1. Nếu bạn muốn đặt dữ liệu trong savedState, sử dụng:
savedStateHandle.set("key_name","value")
  1. Nếu bạn muốn lấy dữ liệu từ savedState, sử dụng:
savedStateHandle.get("key_name")
  1. Nếu bạn muốn nhận LiveData như kiểu trả về, sử dụng:
savedStateHandle.getLiveData("key_name")
  1. Nếu bạn muốn kiểm tra xem khóa cụ thể có tồn tại trong savedState không, sử dụng:
savedState.contains("key_name")

và nó trả về kiểu dữ liệu boolean, khi đúng có nghĩa là savedState chứa giá trị và khi sai có nghĩa là không chứa.

  1. Nếu bạn muốn tìm tất cả các khóa trong savedState, sử dụng đoạn mã sau, nó sẽ trả về danh sách các khóa.
savedState.keys()
  1. Và để xóa bất kỳ giá trị cụ thể nào, bạn có thể truy cập nó thông qua khóa của nó để xóa nó. Để làm điều này, sử dụng:
savedState.remove("key_name")

Kết luận

Cảm ơn bạn đã đọc tới đây, hi vọng thông qua bài viết này bạn đã nắm được về SaveState của ViewModel và có thể ứng dụng nó thành công trong dự án Android của bạn. Thanks!

Nguồn tham khảo: https://blog.mindorks.com/viewmodel-with-savedstate/


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.