In my personal project, I'm using Hilt (dependency injection), SavedStateHandle and ViewModel.

SavedStateHandle which allows me to save data across process death.

Because ViewModel doesn't survive across process death, I'm injecting inside the ViewModel the savedStateHandle object using Hilt injection.

when using coroutines/viewModelScope to handle http requests, I'm using a custom class "ViewState" to handle the multiple state in just one LiveData which is observed from UI.

For example : LiveData<ViewState<User>> and then in UI, I'm binding the viewState : viewMvc.bind(userViewState)

sealed class ViewState<T> : Serializable {

    data class Loading<T>(@Transient val job: Job? = null, val oldResult: T? = null) : ViewState<T>(), Serializable
    data class Fetched<T>(val result: T, val fetchedDate: Date = Date()) : ViewState<T>(), Serializable
    data class Error<T>(val e: Exception, val oldResult: T? = null) : ViewState<T>(), Serializable

    override fun toString(): String {
        return when (this) {
            is Loading -> "ViewState.Loading - oldResult: $oldResult"
            is Fetched -> "ViewState.Fetched - result: $result, fetched date: $fetchedDate"
            is Error -> "ViewState.Error - class: ${e::class.java.simpleName}, message: ${e.localizedMessage?.toString()}"
        }
    }

    fun lastResult(): T? {
        return when (this) {
            is Fetched -> result
            is Loading -> oldResult
            is Error -> oldResult
        }
    }
}

In ViewState.Loading class, job is transient because it is not serializable. And before using SavedStateHandle in viewModel, I didn't need to serialize the LiveData value because it was initialized to null when ViewModel was created.

The problem is

When the app process is killed while the viewState of user is ViewState.Loading, viewModelScope is no longer alive and the job associated to it is cancelled in onCleared() from ViewModel.

Naively, I thought that when the "onCleared" event occurs, I had to just change the value of LiveData to null or whatever I want to avoid a ViewState.Loading value across process death but apparently the SavedStateHandle is already saved at this time; because otherwise when the app state is restored, the user view state is ViewState.Loading() and I'm waiting for something which will never happen because the job is already cancelled. When serializing the SavedStateHandle, I should never save a ViewState.Loading object. So in practice "job" variable should not be transient because it will never be serialized.

How can I intercept the exact time when SavedStateHandle is serialized/parcelized to avoid this kind of problem ?

1

There are 1 answers

1
Entreco On

I guess you cannot (unless you do the saving/restoring yourself, inside the Activity/Fragment).

However, instead of preventing to store the Loading state, you could by-pass it once your restore it. E.g. when your restored state is Loading, you could call the loading function again.

// Inside your viewModel
init{
    if(savedState.contains("Loading)){
        viewModelScope.launch{
            loadData()
        }
    }
}