zsmb.coBeta 3



Top 10 Kotlin Stack Overflow questions, pt 2 - the big ones

This is the written version of a talk I gave recently, and since it was quite long, this is the second of three articles about it. If you've missed it, here's the first one and when you're done here, the third one is waiting for you too!


4. SAM conversions

SAM conversions are a very frequent topic of questions. To address these, let's construct a single code example to show off various issues on.

We'll have an OnClickListener interface with a single method declared in Java:

public interface OnClickListener {
    void onClick(Button button);
}

And a Button class which can be given a listener that's called for every click of the button:

public class Button {
    public void setListener(OnClickListener listener) { ... }
}

The full syntax for creating an anonymous listener in Kotlin would look like this:

button.setListener(object: OnClickListener {
    override fun onClick(button: Button) {
        println("Clicked!")
    }
})

Thanks to SAM conversion, you can also pass in just the inner braces and their contents in this case, as a lambda:

button.setListener {
    println("Clicked!")
}
Why is my click listener not being called?

A common mistake when using SAM conversion is not directly putting the code you want to run inside your first set of braces which represent your lambda. This code will work as expected:

button.setListener {
    println("Clicked!")
}

However, none of the following ones will.

This one gives the button a lambda that will create an anonymous instance of an OnClickListener every time the button is clicked, which is then just thrown away (never assigned anywhere, never invoked).

button.setListener {
    object: OnClickListener {
        override fun onClick(button: Button) {
            println("Clicked!")
        }
    })
}

This one declares a useless local function in a very similar fashion:

button.setListener {
    fun onClick(button: Button) {
        println("Clicked!")
    }
}
How do I refer to the anonymous instance if I create it with a SAM conversion?

Sometimes you need to refer to the listener instance itself when it's called, for example, you might want to remove it in some cases. In Java, you could reference the anonymous class with the this keyword. If you use SAM conversion, this is simply not possible. For these cases, you have to use the full object expression syntax:

button.setListener(object: OnClickListener {
    override fun onClick(button: Button) {
        button.removeListener(this)
    }
})
Why is my SAM conversion complaining about a return type?

SAM conversions by their concise nature hide the required return types of the methods that they're overriding. For example, if the OnClickListener interface looked like this instead, with a boolean return to signify whether the click has been handled:

public interface OnClickListener {
    public boolean onClick(Button button);
}

Then this implementation would no longer be valid:

button.setListener {
    println("Clicked!")
}

You'd get the following error on the println statement:

Kotlin: Type mismatch: inferred type is Unit but Boolean was expected

This is a problem on the println statement because lambdas (somewhat controversially) implicitly return their last expression's return value, in this case, Unit. The fix of course is to add a statement that matches the required return type as declared in the interface:

button.setListener {
    println("Clicked!")
    true
}
I can't use SAM conversions for Kotlin interfaces! What do I do now?

It often comes up that SAM conversions only work as described above if both the OnClickListener interface and the Button class (or more importantly, the method that takes the listene If the interface is defined in Java, r parameter) are defined in Java.

If the interface is defined in Java, but the function that takes an instance of it is defined in Kotlin, you'll have to use a more explicit language construct, a SAM constructor:

button.setListener(OnClickListener {
    println("Clicked!")
})

And if your interface is defined in Kotlin, you can do nothing but use a full object expression syntax:

Java interfaceKotlin interface
Java methodSAM conversionObject expression
Kotlin methodSAM constructorObject expression

The explanation in the documentation for this is that in Kotlin we have function types, and we should use those instead - however, doing so leads to occasional weird usage from Java code. Note that the table above doesn't mention usage from Java, since all of the above combinations can simply be used with lambdas there.

In conclusion, if you don't need to support Java, you can just use function types freely in Kotlin, perhaps with type aliases to make your types clearer. If you do have to interop with Java still, define your interfaces in Java, and if you can, also the methods that take these interfaces. This will give you the best experience in both languages.

5. Replacing static things

How can I make a function or variable static?

This turned out to be a way too long and boring topic to explain in a talk like this, but here's the basics quickly. If your class has "non-static" parts as well, place the "static" parts in a companion object:

class Foo {
    companion object {
        fun x() { ... }
    }
    fun y() { ... }
}

If your class was completely static, you can also replace it with a singleton object:

object Foo {
    fun x() { ... }
}

And if you don't want to always scope your calls as Foo.x() but want to just use x() instead, you can use a top level function:

fun x() { ... }

Additionally, if you'll need to call these "static" parts from Java, look into using the @JvmStatic and @JvmName annotations to make those calls nicer. Here's a reference table for what the annotations do to Java calls:

Function declarationKotlin usageJava usage
Companion objectFoo.f()Foo.Companion.f();
Companion object with @JvmStaticFoo.f()Foo.f();
ObjectFoo.f()Foo.INSTANCE.f();
Object with @JvmStaticFoo.f()Foo.f();
Top level functionf()UtilKt.f();
Top level function with @JvmNamef()Util.f();

For variables, the same rules about where to place them apply, and so the the above annotations. Additionally, there's a @JvmField annotation that can be used with variables, plus the const keyword which optimizes calls to compile time constants by inlining their values to the call sites at compilation time. Here's the reference:

Variable declarationKotlin usageJava usage
Companion objectX.xX.Companion.getX();
Companion object with @JvmStaticX.xX.getX();
Companion object with @JvmFieldX.xX.x;
Companion object with constX.xX.x;
ObjectX.xX.INSTANCE.getX();
Object with @JvmStaticX.xX.getX();
Object with @JvmFieldX.xX.x;
Object with constX.xX.x;
Top level variablexConstKt.getX();
Top level variable with @JvmFieldxConstKt.x;
Top level variable with constxConstKt.x;
Top level variable with @JvmNamexConst.getX();
Top level variable with @JvmName and @JvmFieldxConst.x;
Top level variable with @JvmName and constxConst.x;
How can I declare a static initializer?

This is a simpler and much quicker question. If you need to run certain code just once for a class, you can use initializer blocks inside their companion object to do so - these will be translated to regular static initializers.

class X {
    companion object {
        init {
            println("Static initialization!")
        } 
    }
}

Read the final article of the series covering many null handling issues here.