1, Foreword
Although ViewModel has helped us deal with the data retention problem caused by page destruction and reconstruction caused by screen rotation, the data is not saved for page destruction and reconstruction caused by insufficient memory. This article mainly makes a unified improvement and arrangement according to the official documents and examples, supplemented by code examples for demonstration.
2, Add dependency
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.4.0"
3, SavedStateHandle
The ViewModel mainly uses the SavedStateHandle to save data. Here is a simple demonstration of this use.
It is defined as follows:
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
class MainFragment : Fragment() { val vm: SavedStateViewModel by viewModels() ... }
Save and value are as follows:
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 } }
By using SavedStateHandle, the query value will be retained after the process is terminated, so as to ensure that the user can see the same set of filtered data before and after re creation without manually saving, restoring and re transferring the value to ViewModel by Activity or Fragment.
In addition, the SavedStateHandle contains other methods that you may use when interacting with key value pair mappings:
- contains(String key) -Checks whether a value for the given key exists.
- remove(String key) -Removes the value of the given key.
- keys() -Returns all keys contained in the SavedStateHandle.
4, Support type
By default, you can call set() and get() on SavedStateHandle to handle the same data type as Bundle, as shown below.
Type / class support | Array support |
---|---|
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+) |
If the class does not extend any of the items in the above list, you should consider adding @Parcelize Kotlin annotation or direct implementation Parcelable Make the class a Parcelable type.
5, Save non Parcelable classes
If a class does not implement Parcelable or Serializable and cannot be modified to implement one of these interfaces, the instance of the class cannot be directly saved to SavedStateHandle.
from Lifecycle 2.3.0-alpha03 Initially, the SavedStateHandle allows you to save any object by using setSavedStateProvider() Method provides your own logic for using objects as Bundle To save and restore. SavedStateRegistry.SavedStateProvider Is an interface that defines a single saveState() Method to return the Bundle containing the state you want to save. When the SavedStateHandle is ready to save its state, it calls saveState() to retrieve the Bundle from the SavedStateProvider and save the Bundle for the associated key.
Imagine an example application that uses ACTION_IMAGE_CAPTURE Intent requests pictures from the camera application and passes a temporary file for the camera to store pictures. The TempFileViewModel encapsulates the logic for creating the temporary file.
To ensure that temporary files are not lost after the Activity's process terminates and then resumes, TempFileViewModel can use SavedStateHandle to retain its data. To allow TempFileViewModel to save its data, implement SavedStateProvider and set it as a provider on the SavedStateHandle of ViewModel:
To recover the File data when the user returns, retrieve temp from the SavedStateHandle_ File Bundle. This is the bundle containing the absolute path provided by saveTempFile(). This absolute path can then be used to instantiate the new File.
Its reference code is as follows:
//Save status //Exception save status private fun File.saveTempFile() = bundleOf("path" to absolutePath) class SaveStateViewModel(private val state: SavedStateHandle): ViewModel() { private var tempFile: File? = null init { state.setSavedStateProvider("temp_file") { // saveState() tempFile?.saveTempFile() ?: Bundle() } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
6, AbstractSavedStateViewModelFactory
As seen in the previous example, viewModels() can be used to build viewmodels without passing in the corresponding SavedStateHandle instance. However, this constructor can not pass redundant parameters, because we need to adopt a new method when creating a new ViewModel instance using Factory. The example is as follows:
class DetailViewModel( private val githubApi: GithubApi, private val handle: SavedStateHandle ) : ViewModel() { fun loadData() { val id = handle["id"] ?: "default" viewModelScope.launch { val response = githubApi.getCommit(id) // Handle response } } }
class DetailViewModelFactory( private val githubApi: GithubApi, owner: SavedStateRegistryOwner, defaultArgs: Bundle? = null ) : AbstractSavedStateViewModelFactory(owner, defaultArgs) { override fun <T : ViewModel> create( key: String, modelClass: Class<T>, handle: SavedStateHandle ): T { return DetailViewModel(githubApi, handle) as T } }
Use the following:
class DetailActivity : AppCompatActivity() { private val githubApi = GithubApi() private val viewModel by viewModels { DetailViewModelFactory(githubApi, this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Use viewModel here viewModel.loadData() } }
Because ComponentActivity has implemented the SavedStateRegistryOwner interface, you can pass this directly here.