Reputation: 4415
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
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
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