3

I'm working on a multiplatform project and I'm trying to expose an API to add listeners:

interface InputEmitter {

    fun addInputListener(listener: InputListener)

}

InputListener is a SAM interface and it looks like this:

interface InputListener {

    fun onInput(input: Input)
}

My problem is that this works properly from both Java and Kotlin but on the Kotlin side this is not idiomatic, since I have to invoke it like this:

obj.addInputListener(object : InputListener {
    override fun onInput(input: Input) {
        TODO("not implemented")
    }
})

instead of this:

obj.addInputListener {
    TODO("not implemented")
}

In this case the compiler complains about Type mismatch which is ok.

I could have solved this by using the @FunctionalInterface annotation and in that case the Kotlin side would be better but since this is a multiplatform project I can't use that annotation.

Adding two functions:

fun addInputListener(listener: InputListener)

fun addInputListener(listener: (Input) -> Unit)

is not working either because it is ambiguous from the Java side. How can I solve this problem so that it works idiomatically from both Java and Kotlin?

I know that I can do something like this:

fun addInputListener(listener: InputListener)

fun onInput(listener: (Input) -> Unit)

but in that case the Java user will be puzzled when he/she wants to use the onInput variant (because it resolves to Function1 which is not instantiable from the Java side).

Is there a canonical solution for this problem?

Adam Arold
  • 29,285
  • 22
  • 112
  • 207
  • How could you have solved this using `@FunctionalInterface`? Kotlin does not support SAM for Kotlin-defined interfaces, regardless of how they're annotated. – yole Sep 25 '18 at 14:05
  • I could add a Java `interface` which has the annotation then I can use it like this: `addInputListener(InputListener{ // do something })` which is still not perfect but better than the alternative. – Adam Arold Sep 25 '18 at 14:08
  • My current solution is an extension function which does not pollute the Java namespace but with this option I have 2 versions of `addInputListener`: one which is what you see above, and the other which is an extension function and takes a lambda. Is there a better solution? – Adam Arold Sep 25 '18 at 14:10
  • 1
    Defining the API in Java interfaces and doing the implementation in Kotlin would give you sam conversion in both languages. https://gist.github.com/zapl/443ca6272d41db24a992c1c536d7ed45 – zapl Sep 25 '18 at 14:24
  • Yea I know but it is not a solution in a multiplatform project :( – Adam Arold Sep 25 '18 at 14:35
  • Btw, `Function1` can be implemented in Java too, even with a lambda, it's just fairly ugly api and there are quirks like one may have to return [`Unit.INSTANCE`](https://stackoverflow.com/questions/37828790/why-do-i-have-to-return-unit-instance-when-implementing-in-java-a-kotlin-functio) for a `-> Unit` function. Interop at this point is just ugly and your way to use extension functions is already better than what I found so far. Related: https://youtrack.jetbrains.com/issue/KT-7770 discusses adding sam conversion for native kotlin interfaces but if that happens it's not anytime soon. – zapl Sep 25 '18 at 15:25
  • I see. The whole point of this is to make the API easy to use. – Adam Arold Sep 25 '18 at 18:21
  • 1
    You can rewrite the onInput method as an extension function, then it won't be visible from Java. – loc Oct 21 '18 at 07:33
  • Yep, I did exactly that! – Adam Arold Oct 21 '18 at 18:55

0 Answers0