Как правильно обрабатывать функции SAM с помощью Kotlin в мультиплатформенном проекте?

Я работаю над многоплатформенным проектом и пытаюсь предоставить API для добавления слушателей:

interface InputEmitter {

    fun addInputListener(listener: InputListener)

}

InputListener — это SAM-интерфейс и выглядит он так:

interface InputListener {

    fun onInput(input: Input)
}

Моя проблема в том, что это правильно работает как с Java, так и с Kotlin, но на стороне Kotlin это не идиоматично, так как я должен вызывать его следующим образом:

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

вместо этого:

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

В этом случае компилятор жалуется на несоответствие типов, что нормально.

Я мог бы решить эту проблему, используя аннотацию @FunctionalInterface, и в этом случае сторона Kotlin была бы лучше, но, поскольку это мультиплатформенный проект, я не могу использовать эту аннотацию.

Добавление двух функций:

fun addInputListener(listener: InputListener)

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

также не работает, потому что это неоднозначно со стороны Java. Как я могу решить эту проблему, чтобы она идиоматически работала как с Java, так и с Kotlin?

Я знаю, что могу сделать что-то вроде этого:

fun addInputListener(listener: InputListener)

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

но в этом случае пользователь Java будет озадачен, когда он/она захочет использовать вариант onInput (поскольку он разрешается в Function1, который не может быть создан со стороны Java).

Есть ли каноническое решение этой проблемы?


person Adam Arold    schedule 25.09.2018    source источник
comment
Как вы могли решить эту проблему, используя @FunctionalInterface? Kotlin не поддерживает SAM для интерфейсов, определенных Kotlin, независимо от того, как они аннотированы.   -  person yole    schedule 25.09.2018
comment
Я мог бы добавить Java interface с аннотацией, а затем использовать ее так: addInputListener(InputListener{ // do something }), которая все еще не идеальна, но лучше, чем альтернатива.   -  person Adam Arold    schedule 25.09.2018
comment
Мое текущее решение - это функция расширения, которая не загрязняет пространство имен Java, но с этой опцией у меня есть 2 версии addInputListener: одна, которую вы видите выше, и другая, которая является функцией расширения и принимает лямбда. Есть ли лучшее решение?   -  person Adam Arold    schedule 25.09.2018
comment
Определение API в интерфейсах Java и реализация в Kotlin дадут вам одинаковое преобразование на обоих языках. gist.github.com/zapl/443ca6272d41db24a992c1c536d7ed45   -  person zapl    schedule 25.09.2018
comment
Да, я знаю, но это не решение для мультиплатформенного проекта :(   -  person Adam Arold    schedule 25.09.2018
comment
Кстати, Function1 можно реализовать и на Java, даже с лямбдой, это просто довольно уродливый API, и есть причуды, например, может потребоваться вернуть Unit.INSTANCE для функции -> Unit. Interop на данный момент просто уродлив, и ваш способ использования функций расширения уже лучше, чем то, что я нашел до сих пор. Связанный: youtrack.jetbrains.com/issue/KT-7770 обсуждает добавление преобразования sam для родные интерфейсы kotlin, но если это произойдет, то не в ближайшее время.   -  person zapl    schedule 25.09.2018
comment
Я понимаю. Весь смысл в том, чтобы упростить использование API.   -  person Adam Arold    schedule 25.09.2018
comment
Вы можете переписать метод onInput как функцию расширения, тогда он не будет виден из Java.   -  person loc    schedule 21.10.2018
comment
Ага, я именно так и сделал!   -  person Adam Arold    schedule 21.10.2018