How to implement Saved State of Android custom view in Kotlin

How to implement Saved State of Android custom view in Kotlin

Tags
Android
Kotlin
Published
November 29, 2020
Author
Mateusz Teteruk

What’s the problem?

Matt is developing Android custom view using Kotlin language. He wants to handle saved state of this view. There is custom SavedState class which extends View.BaseSavedState. Matt runs the app and gets an error. What’s wrong?
java.lang.RuntimeException: Parcel android.os.Parcel@xxxx: Unmarshalling unknown type code xxxx at offset xxxx
First, he tries to find answers but StackOverflow doesn’t say much about it, in this case everything looks good and should work. Every sample is in Java so Matt writes the same code in Java (ugh!) and runs app again. Everything is fine. What?? Okay, so this time, let the converter do the job. The same result! What’s wrong with this code?
import android.os.Parcel import android.os.Parcelable import android.os.Parcelable.ClassLoaderCreator import android.view.View class SavedState : View.BaseSavedState { constructor(source: Parcel?, loader: ClassLoader?) : super(source, loader) constructor(superState: Parcelable?) : super(superState) companion object { val CREATOR: ClassLoaderCreator<SavedState> = object : ClassLoaderCreator<SavedState> { override fun createFromParcel(source: Parcel): SavedState = SavedState(source, null) override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size) override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState = SavedState(source, loader) } } }

Root cause analysis

Let’s go to the documentation, maybe we will find some hints (https://developer.android.com/reference/android/os/Parcelable.Creator). „Interface that must be implemented and provided as a public CREATOR field that generates instances of your Parcelable class from a Parcel.”
Okay. This is it. With the help of the Android Studio tool which shows Kotlin bytecode, we can spot the difference.
private static final ClassLoaderCreator CREATOR
This is not PUBLIC FIELD! It’s private and is exposed via method. So this breaks contract! Let’s fix this.

Solution

We have simple and elegant solution. It is proper annotation – @JvmField. Thanks to it, we explicitly say that we want this to be public field.
import android.os.Parcel import android.os.Parcelable import android.os.Parcelable.ClassLoaderCreator import android.view.View class SavedState : View.BaseSavedState { constructor(source: Parcel?, loader: ClassLoader?) : super(source, loader) constructor(superState: Parcelable?) : super(superState) companion object { @JvmField val CREATOR: ClassLoaderCreator<SavedState> = object : ClassLoaderCreator<SavedState> { override fun createFromParcel(source: Parcel): SavedState = SavedState(source, null) override fun newArray(size: Int): Array<SavedState?> = arrayOfNulls(size) override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState = SavedState(source, loader) } } }
As a result, after decompiling Kotlin bytecode again, we can see public field CREATOR:
public static final ClassLoaderCreator CREATOR
Now try again running app – everything should be good. You can read more about it here: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-fields. You can find code samples can be found in Github repository: https://github.com/mateuszteteruk/blogposts-samples/tree/master/saved-state-of-custom-view.
Please let me know if there were any situations where Kotlin stabbed you in your eye and it wasn’t working out of the box when Java did!

PS. Please take a look at my pet project newest update https://mateuszteteruk.pl/dayscounter-2-2-0/.Thanks for any feedback 🙂