Is There A Way To Pass A Function Reference Between Activities?
Solution 1:
If tryAgainFunction
is Serializable
, then you can put it into the bundle
using bundle.putSerializable("try_again_function", tryAgainFunction);
.
It will actually be Serializable
if it is a function reference (SomeClass::someFunction
) or a lambda. But it might not be, if it is some custom implementation of the functional interface () -> Unit
, so you should check that and handle the cases when it's not.
Solution 2:
Maybe too late but:
Have you tried to put the val callback
inside the companion object?
Solution may be something like:
companionobject {
privatelateinitvar callback
funnewInstance(tryAgainFunction: () -> Unit): TimeOutHandlerFragment {
val fragment = TimeOutHandlerFragment()
this.callback = tryAgainFunction
return fragment
}
}
overridefunonViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnTryAgain.setOnClickListener { callback.invoke() }
}
Solution 3:
It is possible in 2021.
1. You need to add id 'kotlin-parcelize'
to your build.gradle (app), like this:
plugins {
id'com.android.application'id'kotlin-android'id'kotlin-kapt'id'kotlin-parcelize' //here
}
2. Create a Class to wrap your Lambda function:
@ParcelizeclassListener(val clickAction: () -> Unit) : Parcelable {
funonClick() = clickAction()
}
3. Pass your Lambda:
val bundle = Bundle().apply {
putParcelable("listener", Listener(clickAction))
}
privateval clickAction: () -> Unit = {
//do your stuff
}
4. Retrieve it:
arguments?.getParcelable<Listener>("listener")?.onClick()
Demo (all Fragment
s): https://youtu.be/0OnaW4ZCnbk
Solution 4:
No, as others say, there is no way of passing a function literal to Bundle
.
But since enum
implements Serializable
, you can pass enum variables that can hold some methods.
Try below codes;
enumclassSampleEnum(val f: () -> Unit) {
Sample0({
Log.d("enum Test", "sample 0")
}),
Sample1({
Log.d("enum Test", "sample 1")
}),
}
in your from-activity (or fragment);
bundle.putSerializable("sample_enum",SampleEnum.Sample0)
in your to-activity (or fragment);
// invoke your fuction
(bundle.getSerializable("sample_enum") as SampleEnum).f()
Solution 5:
Yes, there is a simple solution to this that I've been using in my projects. The idea is to wrap any reference types/objects in a warper class that is Parcalabe
and Serializable
without actually implementing marshaling of the underlying reference type. Here is the wrapper class and how to use it without causing any potential memory leak:
@Parcelize@kotlinx.serialization.Serializable
dataclassTrackedReference<ReferenceType: Any> privateconstructor(
privatevar uniqueID: Int = -1
) : Serializable, Parcelable {
constructor(reference: ReferenceType) : this() {
uniqueID = System.identityHashCode(this)
referenceMap.set(uniqueID, reference)
}
valget: ReferenceType?
get() = referenceMap.get(uniqueID) as? ReferenceType
funremoveStrongReference() {
get?.let { referenceMap.set(uniqueID, WeakReference(it)) }
}
companionobject {
var referenceMap = hashMapOf<Int, Any>()
privateset
}
}
Any object can be wrapped in a TrackedReference
object and passed as serializable or parcelable:
classClassA{
fun doSomething { }
}
// Add instance of ClassA to an intentval objectA = ClassA()
intent.putExtra("key", TrackedReference(objectA))
You can also pass a weak
or soft
reference depending on the use case.
Use case 1:
The object (in this case objectA
) is not guaranteed to be in memory by the time the receiver activity needs to use it (e.g. might be garbage collected).
In this case, the object is kept in memory until you explicitly remove the strong reference.
Pass in an Intent
or Bundle
inside this wrapper and accesse in a straightforward manner, e.g. (psuedocode)
classClassA{
fun doSomething { }
}
// Add instance of ClassA to an intentval objectA = ClassA()
intent.putExtra("key", TrackedReference(objectA))
// Retrieve in another activityval trackedObjectA = intent.getSerializable("key") as TrackedReference<ClassA>
trackedObjectA.get?.doSomething()
// Important:- to prevent potential memory leak,// remove *strong* reference of a tracked object when it's no longer needed.
trackedObjectA.removeStrongReference()
Use case 2:
The object is guaranteed to be in memory when needed, e.g. an activity that is housing a fragment is guaranteed to remain in memory for the lifecycle of the fragment.
Or, we do not care if the object is garbage collected, and we do not want to be the reason for its existence. In this case, pass a weak
or soft
reference when initializing a TrackedReference
instance:
// Removing the strong reference is not needed if a week reference is tracked, e.g. val trackedObject = TrackedReference(WeakReference(objectA))
trackedObject.get?.doSomething()
Its best to understand why Intent
requires Parcelable
or Serializable
and what's the best way to use this workaround in a given scenario.
It's certainly not ideal to serialize objects to allow delegation or callback communication between activities.
Here is the documentation on Intent
but simply put, an Intent
is passed to the Android System which then looks-up what to do with it and in this case starts the next Activity (which can be another app as far as the Android System receiving the intent is concerned). Therefore, the system needs to make sure that everything inside an intent can be reconstructed from parcels.
Rant:
IMO, Intent
as a higher level abstraction for IPC (Inter Process Communication) maybe convenient and efficient internally but at the cost of these limitations.
It's maybe comparing Apples to oranges but in iOS, ViewControllers (similar to Activities) are like any class and can be passed any Type
(value or reference) as an argument. The developer is then responsible to avoid potential reference-cycles that might prevent ARC (memory management) to free unused references.
Post a Comment for "Is There A Way To Pass A Function Reference Between Activities?"