Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to pass a function reference between activities?

Tags:

android

kotlin

is there a way of bundling function references in Kotlin and Android so that the functions can be called from other fragments? For instance, my fragment factory method looks like this:

    fun newInstance(tryAgainFunction: () -> Unit): TimeOutHandlerFragment {
        val fragment = TimeOutHandlerFragment()
        val bundle = Bundle()

        return fragment
    }

I want to be able to save my tryAgainFunction in the bundle for further retrieval.

Thanks a lot!

Edit

In the end, the most suitable solution was using hotkey's answer and then in onViewCreated I initializing a listener with the passed function. The complete code is as follows:

companion object {
    val CALLBACK_FUNCTION: String = "CALLBACK_FUNCTION"

    fun newInstance(tryAgainFunction: () -> Unit): TimeOutHandlerFragment {
        val fragment = TimeOutHandlerFragment()
        val bundle = Bundle()
        bundle.putSerializable(CALLBACK_FUNCTION, tryAgainFunction as Serializable)
        fragment.arguments = bundle
        return fragment
    }
}

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    try {
        val callback: () -> Unit = arguments.getSerializable(CALLBACK_FUNCTION) as () -> Unit
        btnTryAgain.setOnClickListener { callback.invoke() }
    } catch (ex: Exception) {
        // callback has a wrong format
        ex.printStackTrace()
    }
}

Thanks to everyone for your help!

like image 366
Milan Zelenka Avatar asked Aug 10 '17 15:08

Milan Zelenka


2 Answers

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.

like image 142
hotkey Avatar answered Nov 07 '22 13:11

hotkey


2022 Update:

  1. My original approach has a drawback, in my demo if I press the home button to put the app into the background, it will crash with NotSerializableException.
  2. Actually there is another API just for listening changes from one Fragment in the other, that is FragmentResultListener, offical tutorial is here: https://youtu.be/oP-zXjkT0C0?t=468.
  3. If FragmentResultListener cannot meet your need, Shared ViewModel is the final solution in there: https://youtu.be/THt9QISnIMQ.

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:

@Parcelize
class Listener(val clickAction: () -> Unit) : Parcelable {
    fun onClick() = clickAction()
}

3. Pass your Lambda:

val bundle = Bundle().apply {
    putParcelable("listener", Listener(clickAction))
}

private val clickAction: () -> Unit = {    
    //do your stuff
}

4. Retrieve it:

arguments?.getParcelable<Listener>("listener")?.onClick()

Demo (all Fragments): https://youtu.be/0OnaW4ZCnbk

like image 8
Sam Chen Avatar answered Nov 07 '22 14:11

Sam Chen