Jim
Jim

Reputation: 4415

Using lambdas does not compile when trying to pass in a method expecting a SAM interface

I am trying to understand lambdas and Kotlin. I created this trivial example

interface OnClickListener {
    fun onClick(s: String)
}

class Button {
    var clickListener: OnClickListener? = null

    fun setOnClickListener(listener: OnClickListener?) {
        clickListener = listener
    }
    fun click() {
        clickListener?.onClick("hello")
    }
}

fun main(args: Array<String>) {
    val b = Button()
    b.setOnClickListener(
        object : OnClickListener {
            override fun onClick(s: String) {
                println(s)
            }

        }
    )

   /* 
   Variation 1
   val l = {
        s -> println(s)
    }
    b.clickListener = l*/

    /*
    Variation 2

    b.setOnClickListener{
        s -> println(s)
    }
    */

    /*
    Variation 3

    b.clickListener = {
            s -> println(s)
    }
    */

    b.click()
}

So the above code only compiles if I pass an anonymous object. But I wanted to figure out how to use the lambdas. None of the 3 variation to use a lambda compiles.
I thought since the OnClickListener is a SAM I should easily be able to pass in a lambda What am I doing wrong here?

Upvotes: 0

Views: 192

Answers (2)

Taseer
Taseer

Reputation: 3502

To be able to use a lambda, you need to use a Java interface.

First, create a Java file and create an interface:

public interface OnClickListener {
   void onClick(String s);
}

Then in your main:

   b.setOnClickListener(OnClickListener { s ->
        println(s)
   })

As for your Button class:

class Button {
 var clickListener: OnClickListener? = null //You can use this too but there's another way as well.

 //lateinit var clickListener: OnClickListener //Telling the compiler that you will initialize it later on.

 fun setOnClickListener(listener: OnClickListener) { //removed redundant ? from the function signature.
     clickListener = listener
 }
  fun click() {
     clickListener?.onClick("hello")  //Incase of lateinit, you don't need a '?' anymore
  }
}

SAM conversion only works between a Java code and a Kotlin code.

EDIT: Since in Kotlin, you can store a function in a variable as well, here is my another two cents on how you can do it in a different way:

class Button {
   lateinit var myFunction: (String) -> Unit

   fun setOnClickListener(block : (String) -> Unit) {
      myFunction = block //storing state of your 'listener'
   }

   fun onClick() = myFunction.invoke("Invoked from onClick function")
}

Then in your main:

fun main() {
   val button = Button()
   button.setOnClickListener { s ->
       println(s)
   }

   button.onClick()
}

Upvotes: 3

Leo Aso
Leo Aso

Reputation: 12473

As Taseer Ahmad points out, SAM conversion only works for Java interfaces since Kotlin already has proper function types. Of course, an easy way around this is to simply define a second setOnClickListener method that takes a function type

class Button {
    var clickListener: OnClickListener? = null

    fun setOnClickListener(listener: OnClickListener?) {
        clickListener = listener
    }

    inline fun setOnClickListener(crossinline listener: (String) -> Unit) {
        setOnClickListener(object : OnClickListener { 
            override fun onClick(s: String) = listener(s)
        })
    }

    fun click() {
        clickListener?.onClick("hello")
    }
}

This then allows you to write b.setOnClickListener { println(it) }. I always inline methods like this as a habit, but it's not really required, so you can remove the inline and crossinline if you want.

Upvotes: 1

Related Questions