savedStateHandle not saving State

4.1k views Asked by At

Following this question I made some simple changes in my app, but it's no working as I expect.

I have a Timer that sends a notification when the timer is done. Clicking this notification restarts the activity, deleting all the timer information, which is stored mainly in the viewModel. For this reason, I decided to use saved State for viewModel.

Here's my viewModel:

class TimerViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

private val _secondsRemaining = savedStateHandle.getLiveData<Long>(SECONDS_REMAINING)
val secondsRemaining : LiveData<Long>
    get() = _secondsRemaining

Here is how I use the viewModel in my Fragment:

private val timerViewModel by viewModels<TimerViewModel>()

When I start the timer, I save the value of the seconds remaining in the LiveData, on every Tick of the clock. When the timer finishes, the app sends the notification and the timer starts again, counting a new cycle:

timer = object : CountDownTimer(timerLengthSeconds * 1000, 1000){
        override fun onFinish(){
            (....)
        }

        override fun onTick(millisUntilFinished: Long) {
            var secondsRemainingInCountdown = millisUntilFinished / 1000

            (...)

            _secondsRemaining.value = secondsRemainingInCountdown
         
        }
    }.start()
}

So, when the timer finishes, the app sends the notification but the timer has restarted, and the seconds remaining are getting updated (I've checked this via Logs). When the user clicks the notification the activity gets killed and restarts, and the expactation would be to see the timer with the seconds remaining saved in the LiveData. But when the activity restarts, LiveData value is null.

I have also tried setting a value of 10, in case LiveData is null when first created

private val _secondsRemaining = savedStateHandle.getLiveData<Long>(SECONDS_REMAINING, 10)

but when the activity restarts, I get 10 as the value of LiveData

I can't figure out the reason.

My second problem is that I want to save the state of a LiveData that stores a custom class, that saves the state of the clock

private val _timerState = MutableLiveData<TimerState>()
val timerState : LiveData<TimerState>
    get() = _timerState

Being this class:

    sealed class TimerState {

    object OnFocusRunning : TimerState()
    object OnRestRunning : TimerState()
    object OnFocusPaused : TimerState()
    object OnRestPaused : TimerState()
    object Completed : TimerState()
    object RestCompleted : TimerState()
    object NotStarted : TimerState()
}

But I haven't succeed in this, since TimerState is a custom class and not a primitive type.

2

There are 2 answers

11
cactustictacs On BEST ANSWER

When you're using a SavedStateHandle you need to set your values on it to store them. If you use getLiveData for a particular key, then that LiveData will update whenever you set a new value for that key. If you're setting it directly on the LiveData, you're bypassing the saved state:

class TimerViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

    // create a LiveData that monitors this key in the saved state
    private val _secondsRemaining = savedStateHandle.getLiveData<Long>(SECONDS_REMAINING)
    val secondsRemaining : LiveData<Long> get() = _secondsRemaining

    // expose a setter that updates the state - this will propagate to the LiveData
    fun setSecondsRemaining(remaining: Long) {
        savedStateHandle[SECONDS_REMAINING] = remaining
    }

}

As for your other problem, yeah you're limited in what you can store, and for custom classes you either need to serialise them into a form you can store, or make them Serializable or Parcelable.

In your case though, since that sealed class isn't doing anything special except being an instance of a type, I'd just make it an enum class instead - those are Serializable so you can throw the value straight in there!

enum class TimerState {
    OnFocusRunning, OnRestRunning // etc
}
0
qkx On

SavedStateHandle is not very useful. It only works in one specific scenario - when system kills my app in the background.

But what if user kills my app, or clears all apps stack, or restarts their device? Those are equally if not more common scenarios, and usually happen on a daily basis, and in all of such scenarios, SavedStateHandle won't help.

I think official docs doesn't do a good job explaining this, from the comments in this thread and all over stackoverflow it seems most people misunderstood SavedStateHandle. Me too. It should not be used to persist the app data, in fact it just (temporarily) persists session data.

Since most real-world apps usually must cover all aforementioned scenarios by for example persisting data in SharedPreferences or DataStore or Room, I really don't see many reasons to even bother with SavedStateHandle in most cases...Just skip it and use SharedPreferences and you'll save yourself time & keep your code simple.