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 của
ViewModel
có thể xử lý
thay đổi về cấu hình nên bạn không cần lo về trạng thái trong 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 quy trình kết thúc không mong muốn do khởi động hệ thống,
có thể bạn sẽ muốn dùng
onSaveInstanceState()
để sao lưu.
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
hoạt động nên hãy sử dụng onSaveInstanceState()
yêu cầu một số 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
nhận được một đối tượng
SavedStateHandle
qua hàm dựng. Đố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 đối tượng tương tự.
Thiết lập
Bắt đầu bằng 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 dựng cho ViewModel
của bạn.
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 sử dụng SavedStateHandle
, giá trị truy vấn sẽ được giữ lại quy trình kết thúc không mong muốn
để đả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 có các phương thức khác có thể hữu ích với bạn khi tương tác
với sơ đồ 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ề toàn bộ khoá có trongSavedStateHandle
.
Ngoài ra, bạn có thể truy xuất các giá trị từ SavedStateHandle
bằng cách sử dụng
chủ sở hữu dữ liệu có thể quan sát. Danh sách các loại được hỗ trợ là:
LiveData
Truy xuất các giá trị từ SavedStateHandle
được gói trong một
LiveData
có thể quan sát bằng cách sử 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 để
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 UserViewModelJava(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
Truy xuất các giá trị từ SavedStateHandle
được gói trong một
StateFlow
có thể quan sát bằng cách sử dụng
getStateFlow()
.
Khi bạn cập nhật giá trị của khóa, StateFlow
sẽ nhận được giá trị mới. Thông
thường, bạn có thể đặt giá trị do các 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 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 mềm lifecycle-viewmodel-compose
cung cấp API
saveable
thử nghiệm cho phép tương tác giữa SavedStateHandle
và
Saver
của Compose để bất kỳState
mà bạn
có thể lưu qua rememberSaveable
với một Saver
tùy chỉnh cũng có thể được lưu bằng SavedStateHandle
.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val 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 dữ liệu đượ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
.
Bắt đầu bằng
Vòng đời 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 thành
Bundle
bằng cách dùng 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 chụp ả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
ả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; } } }
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.