When implementing state restoration in Android, how can I save and restore a lambda?
I tried saving it as Serializable and Parcelable, but it throws a compile error.
Is there any way to save and restore them, or should I seek other approaches?
When implementing state restoration in Android, how can I save and restore a lambda?
I tried saving it as Serializable and Parcelable, but it throws a compile error.
Is there any way to save and restore them, or should I seek other approaches?
Kotlin lambdas implement Serializable
, so they can be saved like:
override fun onSaveInstanceState(outState: Bundle) {
outState.putSerializable("YOUR_TAG", myLambda as Serializable)
super.onSaveInstanceState(outState)
}
Similarly, to restore them:
override fun onCreate(savedInstanceState: Bundle?) {
myLambda = savedInstanceState?.getSerializable("YOUR_TAG") as (MyObject) -> Void
super.onCreate(savedInstanceState)
}
(This can obviously be done in any of the lifecycle events that offer you the savedInstanceState
, as this was just an example)
Some notes:
import java.io.Serializable
is required.Unchecked cast: Serializable? to YourLambdaType
. This cast is safe (assuming you infer the nullability correctly!), so you can safely suppress this warning by using @Suppress("UNCHECKED_CAST")
MyObject
must be Serializable
or Parcelable
, otherwise it crashes at runtime.Now there's a detail that is not told anywhere and crashes in runtime with no helpful crash logs. The inner implementation of your lambda (i.e. what's inside the { }
when you assign it) must not have references to objects that will be deallocated in a later moment.
A classic example would be:
// In your MyActivity.kt…
myLambda = { handleLambdaCallback() }
…
private fun handleLambdaCallback() {
…
}
This will crash in runtime because handleLambdaCallback
is implicitly accessing this
, which would trigger an attempt to recursively serialize the entire object graph reachable by it, which would fail at some point during serialization time.
One solution to this problem is to send a reference in the lambda. Example:
// In your MyActivity.kt…
myLambda = { fragment -> (fragment.activity as MyActivity).handleLambdaCallback() }
…
private fun handleLambdaCallback() {
…
}
This way, we are computing the reference when the lambda is invoked, rather than when it's assigned. Definitely not the cleanest solution, but it's the best I could come with, and it works.
Feel free to suggest improvements and alternative solutions!
Serializable
should be that this signals to Kotlin to create a serializable lambda in the first place. Lambdas aren't serializable by default. –
Hilten Update for Kotlin 2.0.0+
If you're using Kotlin 2.0.0 or higher, lambas no longer implement Serializable by default. The changelog states:
To retain the legacy behavior of generating lambda functions, you can either:
Annotate specific lambdas with @JvmSerializableLambda.
Use the compiler argument
-Xlambdas=class
to generate all lambdas in a module using the legacy method.
Should I seek other approaches?
Yes, there is not a really good reason to do it, your code won't be easily testable and you could introduce memory leaks.
Instead of saving the function, save the parameters (i.e. variables in the scope) that are needed to be saved, and invoke the function as you usually do.
Example
Instead of doing
val name = "John Smith"
val sayHello = { "Hi there, $name" }
startActivity(Intent().apply { putExtra("GREETER", sayHello as Serializable) })
Create a function that you can use elsewhere
fun sayHello(name: String) = { "Hi there, $name" }
And invoke with the restored name
parameter later
this
. I think your approach, however, makes the child (e.g. fragment) aware of its parent (i.e. the fragment would have to call (activity as MyActivity).sayHello(name)
), which's obviously not good. Either this, or we'd lead to "delegation" (listener) approach. Am I missing something? –
Irresolution interface
as the contract, otherwise is really unclear what the protocol is (whats functions should the fragment/activity call) –
Salvia There are various alternatives,
You may reassign the lambdas on the parent's onAttachFragment method, or via callbacks on the fragment's onAttach method.
You may create a ViewModel for the fragment that hosts that data so that it can be saved between states
You may use a FragmentFactory that receives the object with lambdas so that new fragments recreated regain access to that data which is not destroyed from the factory.
There's an old deprecated way of simply using retainInstance so that you don't care of fragment being destroyed during these state changes. Of course this consumes more data for your app.
© 2022 - 2024 — McMap. All rights reserved.
this
, it will try to recursively serialize the entire object graph reachable from it. I expect that to fail right away, not at deserialization time. The referent being GCd is not a problem because the whole point of parcelization is reconstructing dead objects. – Hilten