Mô-đun Trạng thái đã lưu của ViewModel Một phần của Android Jetpack.
Như đã đề cập trong phần Lưu trạng thái giao diện người dùng, các đối tượng ViewModel có thể xử lý các thay đổi về cấu hình, vì vậy, bạn không cần lo lắng về trạng thái khi ở chế độ xoay hoặc các trường hợp khác. Tuy nhiên, nếu cần xử lý tình huống bị buộc tắt do hệ thống gây ra, có thể bạn sẽ muốn dùng API SavedStateHandle để dự phòng.
Trạng thái giao diện người dùng thường được lưu trữ hoặc có thể tham chiếu trong các đối tượng ViewModel, nên việc sử dụng rememberSaveable trong Compose yêu cầu một số mã nguyên mẫu mà mô-đun trạng thái đã lưu có thể xử lý giúp bạn.
Khi sử dụng mô-đun này, các đối tượng ViewModel sẽ nhận được một đối tượng SavedStateHandle thông qua hàm khởi tạo của nó. Đối tượng này là một bản đồ khoá-giá trị cho phép bạn
viết và truy xuất đối tượng đến và đi từ trạng thái đã lưu. Các giá trị này vẫn tồn tại sau khi hệ thống loại bỏ quy trình và duy trì thông qua cùng một đối tượng.
Trạng thái đã lưu gắn liền với ngăn xếp tác vụ. Nếu ngăn xếp tác vụ biến mất, trạng thái tác vụ đã lưu cũng sẽ biến mất. Điều này có thể xảy ra khi buộc một ứng dụng dừng, xoá ứng dụng khỏi trình đơn gần đây hoặc khởi động lại thiết bị. Trong những trường hợp như vậy, ngăn xếp tác vụ sẽ biến mất và bạn không thể khôi phục thông tin ở trạng thái đã lưu. Trong các trường hợp đóng trạng thái giao diện người dùng do người dùng gây ra, trạng thái đã lưu sẽ không được khôi phục. Trong các trường hợp do hệ thống gây ra, trạng thái đã lưu sẽ được khôi phục.
Thiết lập
Để sử dụng SavedStateHandle, hãy chấp nhận SavedStateHandle làm đối số hàm khởi tạo cho ViewModel.
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Sau đó, bạn có thể truy xuất một phiên bản ViewModel trong các thành phần kết hợp mà không cần cấu hình bổ sung. Nhà máy ViewModel mặc định sẽ cung cấp SavedStateHandle phù hợp cho ViewModel của bạn.
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
Khi cung cấp một phiên bản ViewModelProvider.Factory tuỳ chỉnh, bạn có thể cho phép sử dụng SavedStateHandle bằng cách sử dụng CreationExtras và DSL viewModelFactory.
Làm việc với SavedStateHandle
Lớp SavedStateHandle là một bản đồ khoá-giá trị cho phép bạn ghi và truy xuất dữ liệu đến và đi từ trạng thái đã lưu thông qua phương thức set() và get().
Bằng cách dùng SavedStateHandle, giá trị truy vấn sẽ được giữ lại khi ứng dụng bị buộc tắt để đảm bảo người dùng xem được cùng một nhóm dữ liệu đã lọc trước và sau khi tạo lại mà không có hoạt động hoặc mảnh cần lưu, khôi phục thủ công và chuyển tiếp giá trị đó trở lại ViewModel.
SavedStateHandle cũng cung cấp các phương thức khác mà bạn có thể thấy khi tương tác với bản đồ khoá-giá trị:
contains(String key)– Kiểm tra xem khoá đã cấp có giá trị hay không.remove(String key)– Xoá giá trị của khoá đã cho.keys()– Trả về mọi khoá có trongSavedStateHandle.
Ngoài ra, nếu muốn truy xuất các giá trị từ SavedStateHandle, hãy dùng phần tử giữ dữ liệu có thể ghi nhận được. Danh sách các loại được hỗ trợ bao gồm:
StateFlow
Bạn có thể truy xuất các giá trị từ SavedStateHandle được gói trong một đối tượng có thể quan sát StateFlow. Tuỳ thuộc vào việc bạn có cần trực tiếp thay đổi giá trị hay không, bạn có thể chọn giữa luồng chỉ đọc hoặc luồng có thể thay đổi:
getStateFlow(): Sử dụng phương thức này nếu bạn chỉ cần đọc trạng thái. Khi bạn cập nhật giá trị của khoá ở nơi khác trongSavedStateHandle, StateFlow sẽ nhận được giá trị mới. Đây là lựa chọn lý tưởng khi bạn muốn hiển thị một luồng chỉ đọc và chuyển đổi luồng đó bằng cách sử dụng các toán tử Flow.getMutableStateFlow(): Sử dụng quyền này nếu bạn cần cả quyền đọc và ghi. Việc cập nhật.valuecủaMutableStateFlowđược trả về sẽ tự động cập nhậtSavedStateHandlecơ bản, giúp bạn không cần phải đặt khoá theo cách thủ công.
Thông thường, bạn cập nhật các giá trị này do hoạt động tương tác của người dùng, chẳng hạn như nhập một truy vấn để lọc danh sách dữ liệu.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { // Use getMutableStateFlow to read and write the query directly private val _query = savedStateHandle.getMutableStateFlow("query", "") val query: StateFlow= _query.asStateFlow() // Use getStateFlow if you only need a read-only stream to react to changes val filteredData: StateFlow<List > = query.flatMapLatest { repository.getFilteredData(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun setQuery(newQuery: String) { // Updating the MutableStateFlow automatically updates the SavedStateHandle _query.value = newQuery } }
Hỗ trợ KotlinX Serialization
Đối với trạng thái giao diện người dùng phức tạp, bạn có thể sử dụng đối tượng uỷ quyền thuộc tính saved cùng với KotlinX Serialization. Uỷ quyền này cho phép bạn duy trì các lớp dữ liệu @Serializable tuỳ chỉnh trực tiếp vào SavedStateHandle. Điều này giúp duy trì trạng thái của ViewModel trong quá trình bị buộc tắt, nhờ đó, giao diện người dùng Compose có thể khôi phục trạng thái một cách liền mạch khi được tạo lại.
Để sử dụng, hãy chú thích lớp dữ liệu bằng @Serializable và sử dụng trình uỷ quyền saved trong ViewModel:
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel // Ensure you have the savedstate-ktx dependency import androidx.savedstate.serialization.saved import kotlinx.serialization.Serializable @Serializable data class UserFilterState( val searchQuery: String, val minAge: Int, val includeInactive: Boolean ) class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { // The state is automatically serialized to a Bundle on process death, // and deserialized upon recreation. var filterState by savedStateHandle.saved { UserFilterState(searchQuery = "", minAge = 18, includeInactive = false) } fun updateQuery(newQuery: String) { // Mutating the property automatically updates the underlying SavedStateHandle filterState = filterState.copy(searchQuery = newQuery) } }
Hỗ trợ trạng thái Compose
Nếu trạng thái của bạn dựa vào các API Saver của Compose thay vì dựa vào KotlinX Serialization, thì cấu phần phần mềm lifecycle-viewmodel-compose sẽ cung cấp uỷ quyền saveable. Điều này cho phép SavedStateHandle và Saver của Compose có khả năng tương tác với nhau để bất kỳ State nào mà bạn có thể lưu qua rememberSaveable với một Saver tuỳ chỉnh cũng lưu được bằng SavedStateHandle.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
Các kiểu được hỗ trợ
Dữ liệu có trong SavedStateHandle được lưu và khôi phục dưới dạng Bundle, cùng với phần còn lại của savedInstanceState cho ứng dụng của bạn.
Các loại được hỗ trợ trực tiếp
Theo mặc định, bạn có thể gọi set() và get() trên SavedStateHandle cho
các loại dữ liệu tương tự Bundle như sau:
| Hỗ trợ loại/lớp | Hỗ trợ mảng |
double |
double[] |
int |
int[] |
long |
long[] |
String |
String[] |
byte |
byte[] |
char |
char[] |
CharSequence |
CharSequence[] |
float |
float[] |
Parcelable |
Parcelable[] |
Serializable |
Serializable[] |
short |
short[] |
SparseArray |
|
Binder |
|
Bundle |
|
ArrayList |
|
Size (only in API 21+) |
|
SizeF (only in API 21+) |
Nếu lớp không mở rộng một trong các lớp trong danh sách trên, hãy cân nhắc việc tạo lớp theo gói bằng cách thêm chú thích @Parcelize trong Kotlin hoặc triển khai Parcelable trực tiếp.
Lưu các lớp không theo gói
Nếu một lớp không triển khai Parcelable hoặc Serializable và không thể
sửa đổi để triển khai một trong các giao diện, bạn không trực tiếp
lưu được bản sao của lớp đó vào SavedStateHandle.
Kể từ Lifecycle 2.3.0-alpha03, SavedStateHandle cho phép bạn lưu mọi đối tượng bằng cách cung cấp logic của riêng bạn để lưu và khôi phục đối tượng dưới dạng Bundle bằng phương thức setSavedStateProvider().
SavedStateRegistry.SavedStateProvider là một giao diện xác định một phương thức saveState() duy nhất trả về một Bundle chứa trạng thái bạn muốn lưu. Khi SavedStateHandle đã sẵn sàng lưu trạng thái, nó sẽ gọi saveState() để truy xuất Bundle từ SavedStateProvider và lưu Bundle cho khoá được liên kết.
Hãy xem xét ví dụ về một ứng dụng yêu cầu hình ảnh từ ứng dụng Máy ảnh thông qua ý định ACTION_IMAGE_CAPTURE, truyền tệp tạm thời để xác định vị trí mà máy ảnh nên lưu hình ảnh. TempFileViewModel đóng gói logic để tạo tệp tạm thời đó.
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Để đảm bảo tệp tạm thời không mất đi nếu quy trình hoạt động bị dừng không mong muốn
và khôi phục về sau, TempFileViewModel có thể sử dụng SavedStateHandle để
duy trì dữ liệu. Để cho phép TempFileViewModel lưu dữ liệu, hãy triển khai
SavedStateProvider và đặt làm trình cung cấp trên SavedStateHandle của
ViewModel:
private fun File.saveTempFile() = bundleOf("path", absolutePath) class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Để khôi phục dữ liệu của File khi người dùng quay lại, hãy truy xuất temp_file
Bundle từ SavedStateHandle. Đây là cùng một Bundle do
saveTempFile() cung cấp có chứa đường dẫn tuyệt đối. Sau đó, đường dẫn tuyệt đối có thể
dùng để tạo File mới.
private fun File.saveTempFile() = bundleOf("path", absolutePath) private fun Bundle.restoreTempFile() = if (containsKey("path")) { File(getString("path")) } else { null } class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { val tempFileBundle = savedStateHandle.get<Bundle>("temp_file") if (tempFileBundle != null) { tempFile = tempFileBundle.restoreTempFile() } savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
SavedStateHandle trong hoạt động kiểm thử
Để kiểm thử một ViewModel lấy SavedStateHandle làm phần phụ thuộc, hãy tạo thực thể mới cho SavedStateHandle có giá trị kiểm thử theo yêu cầu và truyền nó vào thực thể ViewModel mà bạn đang kiểm thử.
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
Tài nguyên khác
Để biết thêm thông tin về mô-đun Trạng thái đã lưu của ViewModel, hãy xem
các tài nguyên sau đây.
Lớp học lập trình
Xem nội dung
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Lưu trạng thái giao diện người dùng
- Làm việc với đối tượng dữ liệu có thể ghi nhận được
- Tạo ViewModel có phần phụ thuộc