189

I am converting some of my Java code to Kotlin and I do not quite understand how to instantiate interfaces that are defined in Kotlin code. As an example, I have an interface (defined in Java code):

public interface MyInterface {
    void onLocationMeasured(Location location);
}

And then further in my Kotlin code I instantiate this interface:

val myObj = new MyInterface { Log.d("...", "...") }

and it works fine. However, when I convert MyInterface to Kotlin:

interface MyInterface {
    fun onLocationMeasured(location: Location)
}

I get an error message: Interface MyListener does not have constructors when I try to instantiate it - though it seems to me that nothing has changed except syntax. Do I misunderstand how interfaces work in Kotlin?

8 Answers 8

290

Your Java code relies on SAM conversion - an automatic conversion of a lambda into an interface with a single abstract method. SAM conversion is currently not supported for interfaces defined in Kotlin. Instead, you need to define an anonymous object implementing the interface:

val obj = object : MyInterface {
    override fun onLocationMeasured(location: Location) { ... }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Many thanks. From the link you posted, I understand that it is preferable to use functional types (e.g. Location -> Unit) instead of single-method interfaces if possible - is that correct?
It is correct. You should use functional type wherever possible.
However in my case the interface (SurfaceTextureListener) had multiple methods.
Thanks so much. This answer must have more likes or some special mark, couse it is very useful information and unfortunately some learners can become very confused when learn Kotlin by articles or by "Kotlin in Action" when they look at SAM topic.
from "Kotlin in Action", yes, you can use lambda in Java's SAM param for shorten and cleaner code, but not Kotlin's SAM, function type is the first class in Kotlin, so, SAM has not meaning for Kotlin, function type with typealias is more Kotlin style.
|
30

SAM conversion is supported for interfaces defined in Kotlin since 1.4.0

What's New in Kotlin 1.4.0

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.

SAM conversion applies if you pass a lambda as an argument when an interface with only one single abstract method is expected as a parameter. In this case, the compiler automatically converts the lambda to an instance of the class that implements the abstract member function.

So the example in Your question will look something like this:

fun interface MyInterface {
    fun onLocationMeasured(location: String)
}

fun main() {
    val myObj = MyInterface { println(it) }

    myObj.onLocationMeasured("New York")
}

3 Comments

This wrkd for me
+1 for mark a Kotlin interface explicitly as functional with the fun modifier. Nothing works without it.
Yes, you need to add "fun" before interface.
21

The best solution is to use a typealias in-place of your Java interface

typealias MyInterface = (Location) -> Unit

fun addLocationHandler(myInterface:MyInterface) {

}

Register it like this:

val myObject = { location -> ...}
addLocationHandler(myObject)

or even cleaner

addLocationHandler { location -> ...}

Invoke it like this:

myInterface.invoke(location)

The 3 current options seem to be:

  • typealias (messy when called from java)
  • kotlin interface (messy when called from kotlin; you need to create an object) This is a big step back IMO.
  • java interface (less messy when called from kotlin; lambda needs interface name prepended so you don't need an object; also can't use lambda outside of function parenthesis convention)

When converting our libraries to Kotlin, we actually left all the interfaces in Java code, as it was cleaner to call Java from Kotlin than Kotlin from Kotlin.

Comments

15

Try to access to your interface like this :

 object : MyInterface {
    override fun onSomething() { ... }
}

Comments

8

if you have Java class like this :

recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new RecyclerTouchListener.ClickListener()
        {
              //Your Code
        }));

you shoud convert this code from Java to Kotlin like this :

override fun showJozList (list : List<ResponseGetJuzList.Parameter4>) {
        adapter.addData(list)
        jozlist_recycler.addOnItemTouchListener(RecyclerTouchListener(
                activity ,
                jozlist_recycler ,
                object : RecyclerTouchListener.ClickListener
                    {
                          //Your Code
                    }))

convert Java Interface :

new RecyclerTouchListener.ClickListener()

to Kotlin Interface Style:

object : RecyclerTouchListener.ClickListener

Comments

3

If the interface is for a listener method of a class, change the interface definition to the function type. That makes the code more concise. See the following.

Class containing listener definition

// A class
private var mLocationMeasuredListener = (location: Location) -> Unit = {}

var setOnLocationMeasuredListener(listener: (location: Location) -> Unit) {
    mLocationMeasuredListener = listener
}

// somewhere in A class
mLocationMeasuredListener(location)

Another class

// B class
aClass.setOnLocationMeasuredListener { location ->
    // your code
}

Comments

0
class YourClass : YourInterface {  
    override fun getTest() = "test"    
}

interface YourInterface {
    fun getTest(): String
}

val objectYourClass: YourInterface = YourClass()
print(objectYourClass.getTest())

Comments

0

Sometimes you have to instantiate a Java class or interface in Kotlin. One way to do this is by creating an object:

WifiP2pManager.java

public interface ActionListener {
    public void onSuccess();
    public void onFailure(int reason);
}

MainActivity.kt

btnDiscover.setOnClickListener {
    //TODO: do not forget ActivityCompat.checkSelfPermission(...)

    mManager.discoverPeers(mChannel, object: ActionListener {
            override fun onSuccess() {
                connectionStatus.text = "Inici del descobriment"
            }
            override fun onFailure(reason: Int) {
                connectionStatus.text = "Error en el descobriment $reason"
            }
        })
    }

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.