Top 10 Kotlin Stack Overflow questions, pt 2 - the big ones
2018-05-01 • Márton Braun
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 interface | Kotlin interface | |
---|---|---|
Java method | SAM conversion | Object expression |
Kotlin method | SAM constructor | Object 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 declaration | Kotlin usage | Java usage |
---|---|---|
Companion object | Foo.f() |
Foo.Companion.f(); |
Companion object with @JvmStatic |
Foo.f() |
Foo.f(); |
Object | Foo.f() |
Foo.INSTANCE.f(); |
Object with @JvmStatic |
Foo.f() |
Foo.f(); |
Top level function | f() |
UtilKt.f(); |
Top level function with @JvmName |
f() |
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 declaration | Kotlin usage | Java usage |
---|---|---|
Companion object | X.x |
X.Companion.getX(); |
Companion object with @JvmStatic |
X.x |
X.getX(); |
Companion object with @JvmField |
X.x |
X.x; |
Companion object with const | X.x |
X.x; |
Object | X.x |
X.INSTANCE.getX(); |
Object with @JvmStatic |
X.x |
X.getX(); |
Object with @JvmField |
X.x |
X.x; |
Object with const | X.x |
X.x; |
Top level variable | x |
ConstKt.getX(); |
Top level variable with @JvmField |
x |
ConstKt.x; |
Top level variable with const | x |
ConstKt.x; |
Top level variable with @JvmName |
x |
Const.getX(); |
Top level variable with @JvmName and @JvmField |
x |
Const.x; |
Top level variable with @JvmName and const |
x |
Const.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.
You might also like...
Top 10 Kotlin Stack Overflow questions, pt 1 - decisions, decisions
I've been hanging out around the Kotlin tag on Stack Overflow a lot this last year or so. This is the first part of a series covering the most frequently asked questions there. For a start, we'll be taking a look at various decisions around using collections in Kotlin. I'm hoping there's something new for everyone in here.
How I Finally Memorized Modifier Ordering in Compose
For the longest time, I proudly had no idea of how Modifier ordering works, and would just guess and then guess again when something didn't look quite right. Here's how I finally ended up remembering how the ordering works.
The Other Side of Stack Overflow
My experiences and thoughts after using Stack Overflow from the other side for over a year now, answering questions mostly under the Kotlin tag.
Top 10 Kotlin Stack Overflow questions, pt 3 - nulls and such
The third and final part covers topics of nullability and some small extras to wrap up the series.