Passing lambda instead of interface

LambdaKotlin

Lambda Problem Overview


I have created an interface:

interface ProgressListener {
    fun transferred(bytesUploaded: Long)
}

but can use it only as an anonymous class, not lambda

dataManager.createAndSubmitSendIt(title, message,
object : ProgressListener {
	override fun transferred(bytesUploaded: Long) {
		System.out.println(bytesUploaded.toString())
	}
})

I think it should be a possibility to replace it by lambda:

dataManager.createAndSubmitSendIt(title, message, {System.out.println(it.toString())})

But I am getting error: Type mismatch; required - ProgressListener, found - () -> Unit?

What am I doing wrong?

Lambda Solutions


Solution 1 - Lambda

As @zsmb13 said, SAM conversions are only supported for Java interfaces.

You could create an extension function to make it work though:

// Assuming the type of dataManager is DataManager.
fun DataManager.createAndSubmitSendIt(title: String, 
                                      message: String, 
                                      progressListener: (Long) -> Unit) {
    createAndSubmitSendIt(title, message,
        object : ProgressListener {
            override fun transferred(bytesUploaded: Long) {
                progressListener(bytesUploaded)
            }
        })
}
Edit

Kotlin 1.4 introduced function interfaces that enable SAM conversions for interfaces defined in Kotlin. This means that you can call your function with a lambda if you define your interface with the fun keyword. Like this:

fun interface ProgressListener {
    fun transferred(bytesUploaded: Long)
}

Solution 2 - Lambda

Kotlin only supports SAM conversions for Java interfaces.

> ... note that this feature works only for Java interop; since Kotlin > has proper function types, automatic conversion of functions into > implementations of Kotlin interfaces is unnecessary and therefore > unsupported.

-- Official documentation

If you want to use a lambda in the parameter, make your function take a function parameter instead of an interface. (For now at least. Supporting SAM conversions for Kotlin interfaces is an ongoing discussion, it was one of the possible future features at the Kotlin 1.1 live stream.)

Solution 3 - Lambda

UPDATED: September 7th, 2020

As the Kotlin documentation for the Kotlin 1.4 release points out:

> Before Kotlin 1.4.0, you could apply SAM (Single Abstract Method) conversions only when working with Java methods and Java interfaces from Kotlin. From now on, you can use SAM conversions for Kotlin interfaces as well. To do so, mark a Kotlin interface explicitly as functional with the fun modifier.

fun interface Operation1 {
    operator fun invoke(x: String): String
}

fun interface Operation2 {
    fun doSomething(x: Int): String
}

val operation1 = Operation1 { "$it world!" }
val operation2 = Operation2 { "$it world!" }

fun main() {
    // Usage: First sample.
    println(operation1("Hello"))
    println(operation2.doSomething(0))
    // Usage: Second sample.
    println(Operation1 { "$it world!" }("Hello"))
    println(Operation2 { "$it!" }.doSomething(0))
}

You can read more about functional interfaces here.

Previous solution:

Declare a typealias, inject it somewhere and invoke it later on:

internal typealias WhateverListener = (String) -> Unit

and then we inject that typealias to our class:

class Gallery constructor(private val whateverListener: WhateverListener) {
    
    ...
    
    galleryItemClickListener.invoke("hello")

    ...
}

so we have our lambda:

val gallery = Gallery { appNavigator.openVideoPlayer(it) }

Credits to my colleague Joel Pedraza, who showed me the trick while trying to find a solution <3.

NOTE: Check out the Kotlin documentation in case you want to know when to use either functional interfaces (lambdas) or type aliases.

Solution 4 - Lambda

A little late to the party: instead of making an interface, you let the compile create one by taking a function directly instead of an interface in your datamanager, like this:

fun createAndSubmitSendIt(title: String, message: String, transferred: (Long) -> Unit) {
    val answer = TODO("whatever you need to do")
    transferred(answer)
}

and then you just use it like how you want it! If I remember correctly, what the kotlin/jvm compiler do is the same as making an interface.

Hope it helps!

Solution 5 - Lambda

Kotlin 1.4 and after

Kotlin 1.4 will fix this problem via "Functional Interfaces"

Kotlin Functional Interface

>* Kotlin API: Perfect

  • Kotlin Access: Perfect

  • Java Access: Perfect

    class KotlinApi { fun interface Listener { fun onResponse(response: String) }

      fun demo(listener: Listener) {
          listener.onResponse("response")
      }
    

    }

    fun kotlinConsumer() { KotlinApi().demo { response -> println(response) } }

    public static void javaConsumer(){ new KotlinApi().demo(response -> { System.out.println(response); }); }

Before Kotlin 1.4

There is no single ultimate solution for this problem if you are aiming best access experience from both Kotlin and Java.

If Kotlin developers had not thought SAM conversion for Kotlin interfaces is unnecessary, "Kotlin Interface" method would be ultimate solution.

> https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
> Also note that this feature works only for Java interop; since Kotlin > has proper function types, automatic conversion of functions into > implementations of Kotlin interfaces is unnecessary and therefore > unsupported.

Select the best solution for your use case.

Kotlin Function Type

>* Kotlin API: Perfect

  • Kotlin Access: Perfect

  • Java Access:

  • Auto generated parameter type like Function1 (not a big problem for Java 8 lambda)

  • Verbose return Unit.INSTANCE; instead of void return.

    class KotlinApi { fun demo(listener: (response: String) -> Unit) { listener("response") } }

    fun kotlinConsumer() { KotlinApi().demo { response-> println(response) } }

    public static void javaConsumer() { new KotlinApi().demo(response -> { System.out.println(response); return Unit.INSTANCE; }); }

Kotlin Interface

>* Kotlin API: Additional interface definition.

  • Kotlin Access: Too verbose

  • Java Access: Perfect

    class KotlinApi { interface Listener { fun onResponse(response: String) }

      fun demo(listener: Listener) {
         listener.onResponse("response")
      }
    

    }

    fun kotlinConsumer() { KotlinApi().demo(object : KotlinApi.Listener { override fun onResponse(response: String) { println(response) } }) }

    public static void javaConsumer() { new KotlinApi().demo(response -> { System.out.println(response); }); }

Java Interface

>* Kotlin API: Mixed Java code.

  • Kotlin Access: A little verbose

  • Java Access: Perfect

    class KotlinApi { fun demo(listener: Listener) { listener.onResponse("response") } }

    public interface Listener { void onResponse(String response); }

    fun kotlinConsumer() { KotlinApi().demo { response -> println(response) } }

    public static void javaConsumer() { new KotlinApi().demo(response -> { System.out.println(response); }); }

Multiple Methods

>* Kotlin API: Multiple method implementations

  • Kotlin Access: Perfect if correct method is used. Auto completion suggests verbose method also.

  • Java Access: Perfect. Auto completion does not suggest function type method because of JvmSynthetic annotation

    class KotlinApi { interface Listener { fun onResponse(response: String) }

      fun demo(listener: Listener) {
          demo { response ->
              listener.onResponse(response)
          }
      }
    
      @JvmSynthetic //Prevents JVM to use this method
      fun demo(listener: (String) -> Unit) {
          listener("response")
      }
    

    }

    fun kotlinConsumer() { KotlinApi().demo { response -> println(response) } }

    public static void javaConsumer() { new KotlinApi().demo(response -> { System.out.println(response); }); }

Java API

>* Kotlin API: There is no Kotlin API, all API code is Java

  • Kotlin access: Perfect

  • Java access: Perfect

    public class JavaApi { public void demo(Listener listener) { listener.onResponse("response"); }

      public interface Listener {
          void onResponse(String response);
      }
    

    }

    fun kotlinConsumer() { JavaApi().demo { response -> println(response) } }

    public static void javaConsumer() { new JavaApi().demo(response -> { System.out.println(response); }); }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionViktor SinelnikovView Question on Stackoverflow
Solution 1 - LambdamarstranView Answer on Stackoverflow
Solution 2 - Lambdazsmb13View Answer on Stackoverflow
Solution 3 - LambdacesardsView Answer on Stackoverflow
Solution 4 - LambdaLouis TsaiView Answer on Stackoverflow
Solution 5 - LambdafthdgnView Answer on Stackoverflow