2

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 ?

sokarcreative
  • 536
  • 7
  • 18
  • This is because loading is transient state and shouldn't be part of the parcelled state. Trying to parcel "loading" is proof that multiple independent concepts are being combined. – EpicPandaForce Apr 29 '21 at 20:20
  • @EpicPandaForce And what can I do to intercept and to adapt the parcelled state before it does by himself when still loading some content ? "Entreco" said a solution that when getting back livedata, I should transform it at initialization and indeed it works although not very clean. Is there a way ton handle it properly before parcelling ? – sokarcreative Apr 29 '21 at 23:08
  • Maybe savedStateHandle.setSavedStateProvider.. – sokarcreative Apr 29 '21 at 23:27

1 Answers1

1

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()
        }
    }
}
Entreco
  • 12,738
  • 8
  • 75
  • 95
  • 1
    "unless you do the saving/restoring yourself, inside the Activity/Fragment" The problem is I can't do that in activity/fragment because if I rotate the screen, I had to cancel the request while it should normally continue because the MainViewModel is still alive. "instead of preventing to store the Loading state, you could by-pass it once your restore it" I've thought about this before but it looks dirty but it seems to be the only way at this time. I will wait for a more reasonable answer before according you the solution – sokarcreative Apr 29 '21 at 13:10