'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 |