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't 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 supress this warning by using @Suppress("UNCHECKED_CAST")MyObject must be Serializable or Parcelable, otherwise it crashes in 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!
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
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.