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
dan bukan
aktivitas, sehingga penggunaan onSaveInstanceState()
atau rememberSaveable
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
Mulai dari Fragment 1.2.0
atau dependensi transitifnya
Activity 1.1.0, Anda dapat menerima
SavedStateHandle
sebagai argumen konstruktor untuk ViewModel
.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
Selanjutnya, Anda dapat mengambil instance ViewModel
tanpa konfigurasi
tambahan apa pun. Factory ViewModel
default menyediakan SavedStateHandle
yang sesuai
untuk ViewModel
Anda.
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); ... } ... }
Saat memberikan instance
ViewModelProvider.Factory
kustom, Anda dapat mengaktifkan penggunaan SavedStateHandle
dengan memperluas
AbstractSavedStateViewModelFactory
.
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 adalah:
LiveData
Ambil nilai dari SavedStateHandle
yang digabungkan dalam
observable LiveData
menggunakan
getLiveData()
.
Saat nilai kunci diperbarui, LiveData
akan menerima nilai baru. Biasanya,
nilai ditetapkan karena interaksi pengguna, seperti memasukkan kueri untuk
memfilter daftar data. Nilai yang diperbarui ini selanjutnya dapat digunakan untuk
mengubah 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
Ambil nilai dari SavedStateHandle
yang digabungkan dalam
StateFlow
yang dapat diamati menggunakan
getStateFlow()
.
Saat Anda memperbarui nilai kunci, StateFlow
akan menerima nilai baru. Biasanya,
Anda dapat menetapkan nilai karena interaksi pengguna, seperti memasukkan
kueri untuk memfilter daftar data. Anda kemudian dapat mengubah nilai
yang diperbarui ini menggunakan operator Flow lainnya.
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 } }
Dukungan Status Compose Eksperimental
Artefak lifecycle-viewmodel-compose
menyediakan alat eksperimental
saveable
API yang memungkinkan interoperabilitas antara SavedStateHandle
dan Compose
Saver
sehingga setiap State
yang Anda
dapat disimpan melalui rememberSaveable
dengan Saver
kustom juga dapat disimpan dengan 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 } } }
Jenis yang didukung
Data yang dipertahankan dalam SavedStateHandle
disimpan dan dipulihkan sebagai
Bundle
, beserta
savedInstanceState
lainnya untuk aktivitas atau fragmen tersebut.
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.
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; } }
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
:
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; } } }
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.
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 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.
Kotlin
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
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Menyimpan status UI
- Bekerja dengan objek data yang dapat diamati
- Membuat ViewModel dengan dependensi