'Is there an elegant way to save and restore View state in Kotlin?

Writting custom views that keep their state across configuration changes in Android is verbose, look at the amount of boilerplate code for saving the state of just one field:

private class SavedState : BaseSavedState {
        var amount: Int = 0

        constructor(parcel: Parcel) : super(parcel) {
            amount = parcel.readInt()
        }

        constructor (parcelable: Parcelable?) : super(parcelable)

        override fun writeToParcel(parcel: Parcel, flags: Int) {
            super.writeToParcel(parcel, flags)
            parcel.writeInt(amount)
        }

        companion object CREATOR : Parcelable.Creator<SavedState> {
            override fun createFromParcel(parcel: Parcel): SavedState {
                return SavedState(parcel)
            }

            override fun newArray(size: Int): Array<SavedState?> {
                return arrayOfNulls(size)
            }
        }
    }

Android Extensions plugin provides the @Parcelize annotation that one can use to autogenerate implementations of Parcelable, but in the case of custom views we have to extend from BaseSavedState not directly from Parcelable.

So, having something like this does not compile:

@Parcelize
data class SavedState(val isLoading: Boolean = false): BaseSavedState()

I am wondering if there's a less verbose way of handling state restoration in a custom view. Would appreciate any ideas or suggestions.



Solution 1:[1]

writing a SaveState class is one way to do this that packs all the data that needs to be saved into one class. another way is to just override onSaveInstanceState and onRestoreInstanceState and put your arguments in a bundle. this avoids the boilerplate you mentioned. if you have several arguments just use data class with @Parcelize annotation and save that class using bundle.putParcelable.(instead of the amount in this example). also don't forget to set isSaveEnabled to true.

init { isSaveEnabled = true }

...

override fun onSaveInstanceState(): Parcelable {
   val bundle = Bundle()
   bundle.putInt("amount", amount)
   bundle.putParcelable("superState", super.onSaveInstanceState())
   return bundle
}
override fun onRestoreInstanceState(state: Parcelable) {
 var viewState = state
 if (viewState is Bundle) {
   amount = viewState.getInt("amount", 0)
   viewState = viewState.getParcelable("superState")
 }
 super.onRestoreInstanceState(viewState)
}

Solution 2:[2]

@Parcelize annotation can still be used because the base class that we have to extend BaseSavedState implements Parcelable. Here is an example where I needed to save some duration in a custom view that I defined.

@Parcelize
internal class SavedState(state: Parcelable?, val duration: Long) : BaseSavedState(state)

As you can see, we can use the annotation to only define the state and the fields that we want to save. Once we have the state ready, we can save/restore it like this:

override fun onSaveInstanceState(): Parcelable {
    val parcel = super.onSaveInstanceState()
    return SavedState(parcel, currentDuration.timeInMillis)
}

override fun onRestoreInstanceState(state: Parcelable?) {
    if (state !is SavedState) {
        super.onRestoreInstanceState(state)
        return
    }

    super.onRestoreInstanceState(state.superState)
    setDurationInMillis(state.duration)
}

Starting from this implementation, you could add the Parcelable argument for the state class and pass it to the base class. With this, everything should work properly.

Solution 3:[3]

Building off @Mohsen's answer, here's the most elegant Kotlin solution I could immediately come up with:

override fun onSaveInstanceState(): Parcelable = bundleOf(
    "amount" to amount,
    "superState" to super.onSaveInstanceState()
)

override fun onRestoreInstanceState(state: Parcelable) = super.onRestoreInstanceState(
    if (state is Bundle) {
        amount = state.getInt("amount", 0)
        state.getParcelable("superState")
    } else {
        amount = 0
        state
    }
)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Iulian Popescu
Solution 3 Sterling