ViewModel and process death

Viewed 796

Why it is recommend to use ViewModel architecture component, if it not handle process death?

For example if I save state with onSaveInstanceState the state persists both for configuration change and process death, while ViewModel only survive configuration change, to survive process death need to get the state from ViewModel and use same onSaveInstanceState mechanism.

What do I miss?

1 Answers

Why it is recommend to use ViewModel architecture component, if it not handle process death?

What do I miss?

You were right until Jan 2020 happened, when finally they released viewmodel-savedstate:1.0.0 and then made AndroidX Activity, Fragment and Navigation latest library versions provide SavedStateViewModelFactory as the default ViewModelProvider.Factory.

Now you can obtain a SavedStateHandle into your ViewModel which can help with persisting/restoring stuff across process death.

class MyViewModel(private val savedStateHandle: SavedStateHandle): ViewModel() {
    val someState: MutableLiveData<String> = savedStateHandle.getLiveData("someState")
}

or

class MyAndroidViewModel(application: Application, private val savedStateHandle: SavedStateHandle): AndroidViewModel(application) {
    val someState: MutableLiveData<String> = savedStateHandle.getLiveData("someState")
}

And these would just work via their default constructors as SavedStateViewModelFactory creates them via reflection.

If you need a SavedStateHandle and also additional parameters, then instead of using a default ViewModelProvider.Factory, you can instead extend from AbstractSavedStateViewModelFactory that will give you a SavedStateHandle that actually works and persists/restores across process death automatically.

val viewModel = ViewModelProvider(this, myAbstractSavedStateViewModelFactoryImpl).get(MyViewModel::class.java)

Or with KTX things

private val viewModel by viewModels { myAbstractSavedStateViewModelFactoryImpl }

You can also scope ViewModels to a Jetpack Navigation's NavGraph, that's a bit trickier but doable:

inline fun <reified T : ViewModel> SavedStateRegistryOwner.createAbstractSavedStateViewModelFactory(
    arguments: Bundle,
    crossinline creator: (SavedStateHandle) -> T
): ViewModelProvider.Factory {
    return object : AbstractSavedStateViewModelFactory(this, arguments) {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(
            key: String, modelClass: Class<T>, handle: SavedStateHandle
        ): T = creator(handle) as T
    }
}

inline fun <reified T : ViewModel> Fragment.navGraphSavedStateViewModels(
    @IdRes navGraphId: Int,
    crossinline creator: (SavedStateHandle) -> T
): Lazy<T> {
    // Wrapped in lazy to not search the NavController each time we want the backStackEntry
    val backStackEntry by lazy { findNavController().getBackStackEntry(navGraphId) }

    return createViewModelLazy(T::class, storeProducer = {
        backStackEntry.viewModelStore
    }, factoryProducer = {
        backStackEntry.createAbstractSavedStateViewModelFactory(
            arguments = backStackEntry.arguments ?: Bundle(), creator = creator
        )
    })
}

Then

class MyFragment: Fragment() {
    private val mySharedViewModel by navGraphSavedStateViewModels(R.id.registration_graph) { handle ->
        MySharedViewModel(handle)
    }
}
Related