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. Dalam 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 Anda.
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Selanjutnya, 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 memberikan instance ViewModelProvider.Factory kustom, Anda dapat
mengaktifkan penggunaan SavedStateHandle dengan menggunakan CreationExtras dan DSL
viewModelFactory.
Menangani SavedStateHandle
Class SavedStateHandle adalah peta nilai kunci 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 tertentu.remove(String key)- Menghapus nilai untuk kunci tertentu.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 berikut ini:
StateFlow
Anda dapat mengambil nilai dari SavedStateHandle yang digabungkan dalam observable StateFlow. Bergantung pada apakah Anda perlu mengubah nilai secara langsung, Anda dapat
memilih antara aliran hanya baca atau aliran 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 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 KotlinX Serialization
Untuk status UI yang kompleks, Anda dapat menggunakan delegasi properti saved bersama dengan
KotlinX Serialization. Delegasi ini memungkinkan Anda menyimpan class data @Serializable
kustom langsung ke SavedStateHandle. Hal ini akan mempertahankan status
ViewModel Anda saat terjadi penghentian proses, sehingga UI Compose Anda dapat memulihkan
statusnya dengan lancar saat dibuat ulang.
Untuk menggunakannya, anotasi 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,
bersama dengan 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 dari Lifecycle 2.3.0-alpha03, SavedStateHandle memungkinkan Anda menyimpan
objek apa pun dengan menyediakan logika sendiri untuk menyimpan dan memulihkan objek sebagai
Bundle menggunakan metode setSavedStateProvider().
SavedStateRegistry.SavedStateProvider adalah antarmuka yang menentukan satu metode
saveState() yang menampilkan Bundle berisi status yang ingin Anda simpan. Saat SavedStateHandle siap menyimpan statusnya, saveState() akan dipanggil
untuk mengambil Bundle dari SavedStateProvider dan menyimpan
Bundle untuk kunci terkait.
Mari kita gunakan contoh sebuah aplikasi yang meminta gambar dari aplikasi kamera melalui
intent ACTION_IMAGE_CAPTURE, yang meneruskan file sementara tempat
kamera akan menyimpan gambar. TempFileViewModel merangkum
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
Melihat konten
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Menyimpan status UI
- Bekerja dengan objek data yang dapat diamati
- Membuat ViewModel dengan dependensi