8

When calling Java code from Kotlin, there is SAM conversion so that Java code like this:

adapter.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view, int position) {
        // Do stuff here
    }
});

Can look like this:

adapter.setOnClickListener { view, position ->
    // Do stuff
}

Now, I'm working on a Kotlin project and I want to define a functional interface as an event listener:

interface OnSomeActionListener {

    fun onSomeAction(parameter1: Int, parameter2: String)

}

In SomeClass I have a function to set the listener:

    ...

    private var onSomeActionListener: OnSomeActionListener? = null

    fun setOnSomeActionListener(listener: OnSomeActionListener) {
        onSomeActionListener = listener
    }

    ...

And when I create an instance of this class and try to invoke the setter function, I do it like so:

val thing = SomeClass()

thing.setOnSomeActionListener(object : OnSomeActionListener {
    override fun onSomeAction(parameter1: Int, parameter2: String) {
        // Do stuff here
    }
})

I'm aware that Kotlin has function types therefore doesn't support SAM conversion from various sites such as this one.

I've read a little about function types but I have not used them before.

How would I rewrite my code so that I can invoke the setter function like this?

val thing = SomeClass()

thing.setOnSomeActionListener { parameter1, parameter2 ->
    // Do stuff here
}

.

Farbod Salamat-Zadeh
  • 19,687
  • 20
  • 75
  • 125

4 Answers4

15

A function type looks like this:

(Parameters) -> ReturnType

In your case, instead of using the interface type, you could use (View, Int) -> Unit. It would look something like this:

private var onSomeActionListener: ((View, Int) -> Unit)? = null

fun setOnSomeActionListener(listener: (View, Int) -> Unit) {
    onSomeActionListener = listener
}

private fun callSomeActionListener(view: View, position: Int) {
    onSomeActionListener?.invoke(view, position)
}

Add names

In functional types you can also specify names for the parameters. This doesn't change much under the hood but they can add some clarity here and in the calling code, which is nice.

(view: View, position: Int) -> Unit

Using a type alias

To avoid having to type (View, Int) -> Unit every time, you can define a typealias:

typealias OnSomeActionListener = (view: View, position: Int) -> Unit

So that your code now looks like this again:

private var onSomeActionListener: OnSomeActionListener? = null

fun setOnSomeActionListener(listener: OnSomeActionListener?) {
    onSomeActionListener = listener
}   

And to call it:

val thing = SomeClass()

thing.setOnSomeActionListener { view, position ->
    // Do stuff here
}
RobCo
  • 6,240
  • 2
  • 19
  • 26
0

Well, something like this:

// declare a variable of nullable function type:
var onSomeActionListener: ((Int, String) -> Unit)? = null

// declare higher-order function:
fun setOnSomeActionListener(listener: (Int, String) -> Unit) {
    onSomeActionListener = listener
}

// set listener:
val listener: (Int, String) -> Unit = { p1, p2 -> { /* some stuff */ } }
setOnSomeActionListener(listener)

// or in one line:
setOnSomeActionListener { p1, p2 -> { /* some stuff */ } }

For more info: Higher-Order Functions and Lambdas

Alex Romanov
  • 11,453
  • 6
  • 48
  • 51
0

How about defining function that accepts a function and returns an interface?

fun makeOnSomeActionListener(f: (Int,String) -> Unit) = object : OnSomeActionListener {
    override fun onSomeAction(parameter1: Int, parameter2: String) = f(parameter1, parameter2)
}

The interface delegates its work to f.

Then you can write

val thing = SomeClass()

thing.setOnSomeActionListener(makeOnSomeActionLisener { parameter1, parameter2 ->
  // Do stuff here
})
letrec
  • 539
  • 5
  • 13
0

You can implement Kotlin-written SAM interfaces as lambdas after the 1.4 Kotlin update released in August 2020.

Simply just mark the single abstract method interface with the fun keyword like so:

fun interface OnSomeActionListener {
    fun onSomeAction(parameter1: Int, parameter2: String)
}

Create a set listener function in SomeClass:

class SomeClass {
    private var onSomeActionListener: OnSomeActionListener? = null
    
    fun setOnSomeActionListener(listener: OnSomeActionListener) {
        onSomeActionListener = listener
    }
}

And you will be able to implement/instantiate the OnSomeActionListener interface with a lambda:

class MyClass  {
    val thing = SomeClass()
    
    thing.setOnSomeActionListener { parameter1, parameter2 ->
        // Do stuff here
    }
}
M.Ed
  • 969
  • 10
  • 12