Modul Status Tersimpan untuk ViewModel Bagian dari Android Jetpack.
Seperti yang telah disebutkan dalam Menyimpan Status UI, objek ViewModel dapat menangani
perubahan konfigurasi, sehingga Anda tidak perlu khawatir tentang status dalam rotasi
atau kasus lainnya. Namun, jika Anda perlu menangani penghentian proses
yang diinisiasi sistem, Anda dapat menggunakan SavedStateHandle API sebagai cadangan.
Status UI biasanya disimpan atau dirujuk dalam objek ViewModel, sehingga penggunaan
rememberSaveable di Compose memerlukan beberapa boilerplate yang
dapat ditangani oleh modul status tersimpan untuk Anda.
Saat menggunakan modul ini, objek ViewModel akan menerima objek SavedStateHandle
melalui konstruktornya. Objek ini adalah peta nilai kunci (key value) yang memungkinkan Anda
menulis dan mengambil objek ke dan dari status tersimpan. Nilai ini
dipertahankan setelah proses dihapus oleh sistem dan tetap tersedia
melalui objek yang sama.
Status tersimpan terikat dengan stack tugas Anda. Jika stack tugas Anda hilang, status tersimpan Anda juga akan hilang. Hal ini dapat terjadi saat Anda memaksa aplikasi berhenti, menghapus aplikasi dari menu terbaru, atau memulai ulang perangkat. Dalam kasus semacam ini, stack tugas akan hilang dan Anda tidak dapat memulihkan informasi dalam status tersimpan. Di skenario Penutupan status UI yang diinisialisasi pengguna, status tersimpan tidak dipulihkan. Di skenario yang dimulai oleh sistem, status tersimpan dipulihkan.
Penyiapan
Untuk menggunakan SavedStateHandle, terima sebagai argumen konstruktor ke
ViewModel.
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Kemudian, Anda dapat mengambil instance ViewModel dalam composable
tanpa konfigurasi tambahan. Factory ViewModel default menyediakan
SavedStateHandle yang sesuai untuk ViewModel Anda.
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
Saat menyediakan instance ViewModelProvider.Factory kustom, Anda dapat
mengaktifkan penggunaan SavedStateHandle menggunakan CreationExtras dan
DSL viewModelFactory.
Menangani SavedStateHandle
Class SavedStateHandle adalah key-value map yang memungkinkan Anda menulis dan
mengambil data ke dan dari status tersimpan melalui metode set() dan
get().
Dengan menggunakan SavedStateHandle, nilai kueri akan dipertahankan di seluruh penghentian proses,
sehingga memastikan pengguna melihat kumpulan data yang difilter yang sama sebelum dan setelah
pembuatan ulang tanpa aktivitas atau fragmen perlu secara manual menyimpan, memulihkan,
dan meneruskan nilai tersebut kembali ke ViewModel.
SavedStateHandle juga memiliki metode lain yang dapat Anda gunakan saat berinteraksi
dengan peta nilai kunci:
contains(String key)- Memeriksa apakah ada nilai untuk kunci yang diberikan.remove(String key)- Menghapus nilai untuk kunci yang diberikan.keys()- Menampilkan semua kunci yang berada dalamSavedStateHandle.
Selain itu, Anda dapat mengambil nilai dari SavedStateHandle menggunakan
holder data yang dapat diamati. Daftar jenis yang didukung mencakup:
StateFlow
Anda dapat mengambil nilai dari SavedStateHandle yang digabungkan dalam StateFlow
observable. Bergantung pada apakah Anda perlu mengubah nilai secara langsung, Anda dapat
memilih antara aliran data hanya baca atau yang dapat diubah:
getStateFlow(): Gunakan ini jika Anda hanya perlu membaca status. Saat Anda memperbarui nilai kunci di tempat lain diSavedStateHandle, StateFlow akan menerima nilai baru. Hal ini ideal jika Anda ingin mengekspos aliran data hanya baca dan mengubahnya menggunakan operator Flow.getMutableStateFlow(): Gunakan ini jika Anda memerlukan akses baca dan tulis. Memperbarui.valuedariMutableStateFlowyang ditampilkan akan otomatis memperbaruiSavedStateHandleyang mendasarinya, sehingga Anda tidak perlu menetapkan kunci secara manual.
Biasanya, Anda memperbarui nilai ini karena interaksi pengguna, seperti memasukkan kueri untuk memfilter daftar data.
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 } }
Dukungan Serialisasi KotlinX
Untuk status UI yang kompleks, Anda dapat menggunakan delegasi properti saved bersama
KotlinX Serialization. Delegasi ini memungkinkan Anda mempertahankan class data @Serializable kustom langsung ke SavedStateHandle. Hal ini mempertahankan status
ViewModel Anda selama penghentian proses, sehingga UI Compose dapat memulihkan
statusnya dengan lancar saat dibuat ulang.
Untuk menggunakannya, anotasikan class data Anda dengan @Serializable dan gunakan delegasi saved
di ViewModel Anda:
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) } }
Dukungan Status Compose
Jika status Anda mengandalkan API Saver Compose, bukan KotlinX
Serialization, artefak lifecycle-viewmodel-compose akan menyediakan
delegasi saveable. Hal ini memungkinkan interoperabilitas antara
SavedStateHandle dan Saver Compose sehingga setiap State yang dapat
Anda simpan melalui rememberSaveable dengan Saver kustom juga dapat disimpan dengan
SavedStateHandle.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
Jenis yang didukung
Data yang dipertahankan dalam SavedStateHandle disimpan dan dipulihkan sebagai Bundle,
beserta savedInstanceState lainnya untuk aplikasi Anda.
Jenis yang didukung secara langsung
Secara default, Anda dapat memanggil set() dan get() di SavedStateHandle untuk
jenis data yang sama dengan Bundle, seperti yang ditunjukkan di bawah ini:
| Dukungan Jenis/Class | Dukungan array |
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+) |
Jika class tidak memperluas salah satu yang ada dalam daftar di atas, pertimbangkan untuk
membagi-bagi class dengan menambahkan anotasi Kotlin @Parcelize atau
mengimplementasikan Parcelable secara langsung.
Menyimpan class yang tidak dapat dibagi-bagi
Jika class tidak mengimplementasi Parcelable atau Serializable dan tidak dapat
dimodifikasi untuk mengimplementasikan salah satu antarmuka tersebut, maka Anda tidak dapat
menyimpan instance class tersebut secara langsung ke dalam SavedStateHandle.
Mulai Lifecycle 2.3.0-alpha03, SavedStateHandle memungkinkan Anda menyimpan
objek apa pun dengan menyediakan logika Anda sendiri untuk menyimpan dan memulihkan objek sebagai
Bundle menggunakan metode setSavedStateProvider().
SavedStateRegistry.SavedStateProvider adalah antarmuka yang menentukan satu metode saveState() yang menampilkan Bundle yang berisi status yang ingin Anda simpan. Saat SavedStateHandle siap menyimpan statusnya, class ini akan memanggil
saveState() untuk mengambil Bundle dari SavedStateProvider dan menyimpan
Bundle untuk kunci terkait.
Pertimbangkan contoh aplikasi yang meminta gambar dari aplikasi kamera melalui
intent ACTION_IMAGE_CAPTURE, yang meneruskan file sementara untuk tempat
kamera harus menyimpan gambar. TempFileViewModel mengenkapsulasi
logika untuk membuat file sementara tersebut.
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Untuk memastikan file sementara tidak hilang jika proses aktivitas dihentikan
dan kemudian dipulihkan, TempFileViewModel dapat menggunakan SavedStateHandle untuk
mempertahankan datanya. Untuk mengizinkan TempFileViewModel menyimpan datanya, implementasikan
SavedStateProvider dan tetapkan sebagai penyedia pada SavedStateHandle untuk 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 } } }
Untuk memulihkan data File saat pengguna kembali, ambil temp_file
Bundle dari SavedStateHandle. Ini adalah Bundle yang sama yang disediakan oleh
saveTempFile() berisi jalur absolut. Jalur absolut tersebut kemudian dapat
digunakan untuk membuat instance File baru.
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 dalam pengujian
Untuk menguji ViewModel yang menggunakan SavedStateHandle sebagai dependensi, buat
instance SavedStateHandle baru dengan nilai pengujian yang diperlukan dan teruskan
ke instance ViewModel yang sedang Anda uji.
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
Referensi lainnya
Guna mengetahui informasi modul Status Tersimpan untuk ViewModel lebih lanjut, baca
referensi berikut.
Codelab
Konten penayangan
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Menyimpan status UI
- Bekerja dengan objek data yang dapat diamati
- Membuat ViewModel dengan dependensi