Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 504
Andy Res Avatar asked Aug 01 '19 13:08

Andy Res


2 Answers

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)
}
like image 186
Mohsen Avatar answered Oct 03 '22 07:10

Mohsen


@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.

like image 28
Iulian Popescu Avatar answered Oct 03 '22 09:10

Iulian Popescu