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 của giao diện người dùng, đối tượng ViewModel
có thể xử lý các thay đổi về cấu hình nên 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 đối tượng ViewModel
và không phải là hoạt động, nên việc sử dụng onSaveInstanceState()
hoặc rememberSaveable
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 đối tượng SavedStateHandle
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
Kể từ Mảnh 1.2.0 hoặc phần phụ thuộc bắc cầu Hoạt động 1.1.0, bạn có thể chấp nhận SavedStateHandle
làm đối số hàm khởi tạo cho ViewModel
của mình.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
Sau đó, bạn có thể truy xuất phiên bản ViewModel
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.
Kotlin
class MainFragment : Fragment() { val vm: SavedStateViewModel by viewModels() ... }
Java
class MainFragment extends Fragment { private SavedStateViewModel vm; public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { vm = new ViewModelProvider(this).get(SavedStateViewModel.class); ... } ... }
Khi cung cấp phiên bản
ViewModelProvider.Factory
tuỳ chỉnh, bạn có thể cho phép việc sử dụng SavedStateHandle
bằng cách mở rộng
AbstractSavedStateViewModelFactory
.
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á đã cấp.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ợ là:
LiveData
Nếu muốn truy xuất các giá trị từ SavedStateHandle
được gói trong một LiveData
có thể ghi nhận được, hãy dùng getLiveData()
.
Khi giá trị của khoá đã được cập nhật, LiveData
sẽ nhận được giá trị mới. Thông
thường, giá trị này được đặt do tương tác của người dùng, chẳng hạn như nhập truy vấn để
lọc danh sách dữ liệu. Sau đó, bạn có thể dùng giá trị đã cập nhật này để chuyển đổi LiveData
.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val filteredData: LiveData<List<String>> = savedStateHandle.getLiveData<String>("query").switchMap { query -> repository.getFilteredData(query) } fun setQuery(query: String) { savedStateHandle["query"] = query } }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle savedStateHandle; public LiveData<List<String>> filteredData; public SavedStateViewModel(SavedStateHandle savedStateHandle) { this.savedStateHandle = savedStateHandle; LiveData<String> queryLiveData = savedStateHandle.getLiveData("query"); filteredData = Transformations.switchMap(queryLiveData, query -> { return repository.getFilteredData(query); }); } public void setQuery(String query) { savedStateHandle.set("query", query); } }
StateFlow
Nếu muốn truy xuất các giá trị từ SavedStateHandle
được gói trong một StateFlow
có thể ghi nhận được, hãy dùng getStateFlow()
.
Khi bạn cập nhật giá trị của khoá, StateFlow
sẽ nhận được giá trị mới. Thông thường, bạn có thể đặt giá trị do hoạt động tương tác của người dùng, chẳng hạn như nhập truy vấn để lọc danh sách dữ liệu. Sau đó, bạn có thể chuyển đổi giá trị đã cập nhật này bằng các toán tử Luồng (Flow) khác.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val filteredData: StateFlow<List<String>> = savedStateHandle.getStateFlow<String>("query") .flatMapLatest { query -> repository.getFilteredData(query) } fun setQuery(query: String) { savedStateHandle["query"] = query } }
Hỗ trợ trạng thái của Compose thử nghiệm
Cấu phần phần mềm lifecycle-viewmodel-compose
cung cấp thử nghiệm
saveable
Các API cho phép tương tác giữa SavedStateHandle
và Compose
Saver
để mọi State
mà bạn
có thể lưu qua rememberSaveable
có một Saver
tuỳ chỉnh cũng có thể được lưu bằng SavedStateHandle
.
Kotlin
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 hoạt động hoặc mảnh.
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 bao gồm một trong các lớp nêu 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, với SavedStateHandle
, bạn có thể lưu mọi đối tượng bằng cách đưa ra logic của riêng mình để lưu và khôi phục đối tượng thành Bundle
theo phương thức setSavedStateProvider()
. SavedStateRegistry.SavedStateProvider
là giao diện xác định một phương thức
saveState()
trả về Bundle
chứa trạng thái bạn muốn lưu. Khi đã sẵn sàng để lưu trạng thái,
SavedStateHandle
sẽ gọi saveState()
để truy xuất Bundle
từ SavedStateProvider
và lưu
Bundle
cho khoá đã 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
và chuyể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 đó.
Kotlin
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel() { } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } }
Để đả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
:
Kotlin
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 } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel(SavedStateHandle savedStateHandle) { savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider()); } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider { @NonNull @Override public Bundle saveState() { Bundle bundle = new Bundle(); if (tempFile != null) { bundle.putString("path", tempFile.getAbsolutePath()); } return bundle; } } }
Để 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.
Kotlin
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 } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel(SavedStateHandle savedStateHandle) { Bundle tempFileBundle = savedStateHandle.get("temp_file"); if (tempFileBundle != null) { tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle); } savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider()); } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider { @NonNull @Override public Bundle saveState() { Bundle bundle = new Bundle(); if (tempFile != null) { bundle.putString("path", tempFile.getAbsolutePath()); } return bundle; } @Nullable private static File restoreTempFile(Bundle bundle) { if (bundle.containsKey("path") { return File(bundle.getString("path")); } return null; } } }
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ử.
Kotlin
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
Đề 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