After I pass kotlin function into Bundle
in onSaveInstanceState
I got NotSerializableException:
java.lang.RuntimeException: Parcelable encountered IOException writing serializable object (name = MyActivity$showFragmentA$1)
at android.os.Parcel.writeSerializable(Parcel.java:1447)
at android.os.Parcel.writeValue(Parcel.java:1395)
at android.os.Parcel.writeArrayMapInternal(Parcel.java:665)
at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1330)
at android.os.Bundle.writeToParcel(Bundle.java:1079)
at android.os.Parcel.writeBundle(Parcel.java:690)
at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3269)
at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3632)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5728)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
Caused by: java.io.NotSerializableException: MyActivity
at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1344)
at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1651)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1497)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1461)
at java.io.ObjectOutputStream.writeFieldValues(ObjectOutputStream.java:959)
at java.io.ObjectOutputStream.defaultWriteObject(ObjectOutputStream.java:360)
at java.io.ObjectOutputStream.writeHierarchy(ObjectOutputStream.java:1054)
at java.io.ObjectOutputStream.writeNewObject(ObjectOutputStream.java:1384)
at java.io.ObjectOutputStream.writeObjectInternal(ObjectOutputStream.java:1651)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1497)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:1461)
at android.os.Parcel.writeSerializable(Parcel.java:1442)
at android.os.Parcel.writeValue(Parcel.java:1395)
at android.os.Parcel.writeArrayMapInternal(Parcel.java:665)
at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1330)
at android.os.Bundle.writeToParcel(Bundle.java:1079)
at android.os.Parcel.writeBundle(Parcel.java:690)
at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3269)
at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3632)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5728)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:789)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:679)
My class:
class MyActivity : Activity {
private var lastFragment: (() -> Fragment)? = null
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState?.putSerializable("lastFragment", lastFragment as Serializable)
}
fun showFragmentA() {
lastFragment = { FragmentA() }
// show fragment lastFragment()
}
fun showFragmentB() {
...
}
}
The problem is this. You create a lambda and set it as the value of your function lastFragment
. But what is the lambda? It is a custom class created by the compiler within the MyActivity
class. And it is an inner class, therefore it has a pointer to an instance of MyActivity
which is not serializable. Therefore your instance of the function, the lambda class, has a reference to something that breaks it from being serialized. Look at this:
class MyClass {
var foo: (()->Unit)? = null
fun makeProblem() {
foo = { println("hi") }
}
}
This created MyClass$makeProblem$1
inner class of MyClass
to hold the body of my lambda { println("hi") }
... and all inner classes have pointers to their containing class, so MyClass$makeProblem$1
has a variable of type MyClass
that you cannot see but is obviously there because that allows the code in the lambda to access members of the containing class. Then boom, this breaks serialization.
Libraries that plan to serialize lambdas know about this and special case cutting of this link assuming there is no use of this inner class reference. Apache Spark does this, by basically using introspection to find a specific hidden field and setting it to null. I have a Kotlin example of this somewhere, but it is fragile in case the internals ever changed.
You can also declare your lambda outside of any class to avoid it from being an inner class. Or make sure the containing class is also serializable. Or wrap it with a static class that is serializable. One of these might work, or not, depending on what you want to happen when the lambda (and class) is deserialized later.
If you look at the bytecode generated, you can see this lambda is clearly an inner class:
// ================uy/sotest/MyClass.class =================
// class version 50.0 (50)
// access flags 0x31
public final class uy/sotest/MyClass {
// access flags 0x2
// signature Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: kotlin.jvm.functions.Function0<kotlin.Unit>
private Lkotlin/jvm/functions/Function0; foo
@Lorg/jetbrains/annotations/Nullable;() // invisible
// access flags 0x11
// signature ()Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: kotlin.jvm.functions.Function0<kotlin.Unit> getFoo()
public final getFoo()Lkotlin/jvm/functions/Function0;
@Lorg/jetbrains/annotations/Nullable;() // invisible
...
// access flags 0x11
// signature (Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;)V
// declaration: void setFoo(kotlin.jvm.functions.Function0<kotlin.Unit>)
public final setFoo(Lkotlin/jvm/functions/Function0;)V
@Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
...
// access flags 0x11
public final makeProblem()V
L0
LINENUMBER 7 L0
ALOAD 0
GETSTATIC uy/sotest/MyClass$makeProblem$1.INSTANCE : Luy/sotest/MyClass$makeProblem$1;
CHECKCAST kotlin/jvm/functions/Function0
PUTFIELD uy/sotest/MyClass.foo : Lkotlin/jvm/functions/Function0;
L1
LINENUMBER 8 L1
RETURN
L2
LOCALVARIABLE this Luy/sotest/MyClass; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x1
public <init>()V
...
@Lkotlin/Metadata;( ... )
// access flags 0x18
final static INNERCLASS uy/sotest/MyClass$makeProblem$1 null null
// compiled from: ShowThing.kt
}
// ================uy/sotest/MyClass$makeProblem$1.class =================
// class version 50.0 (50)
// access flags 0x30
// signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;
// declaration: uy/sotest/MyClass$makeProblem$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit>
final class uy/sotest/MyClass$makeProblem$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {
// access flags 0x1041
public synthetic bridge invoke()Ljava/lang/Object;
...
// access flags 0x11
public final invoke()V
...
// access flags 0x0
<init>()V
...
// access flags 0x19
public final static Luy/sotest/MyClass$makeProblem$1; INSTANCE
// access flags 0x8
static <clinit>()V
...
@Lkotlin/Metadata;( ... )
OUTERCLASS uy/sotest/MyClass makeProblem ()V
// access flags 0x18
final static INNERCLASS uy/sotest/MyClass$makeProblem$1 null null
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With