Kotlin promises interoperability with Java. Today(31/03/2020), I found out that we sometimes need to do a little extra to make interoperability with Java seamless and natural using a couple of annotations. I never really had a use case for Java interoperability, mainly because I have been working in Kotlin-only projects since 2018.
I had the chance to rewrite a simple open source library in Kotlin. During the rewrite, I failed to consider that there could be Android apps written in Java that already depend on the library till the owner of the library drew my attention to that fact. In order to not break existing applications that already depend on this library, I had to find a solution. And that's what led me to @JvmOverloads
and @JvmStatic
annotations.
The Java implementation of the library exposes a single static method with three(3) overloads. Rewriting this in Kotlin was quite simple.
// Shutdown.java
public static void now(Activity context){
if (context != null){
init(context, DEFAULT_MESSAGE, DEFAULT_TIMEOUT);
}
}
public static void now(Activity context, String message){
if (context != null && !message.isEmpty()){
init(context, message, DEFAULT_TIMEOUT);
}
}
public static void now(Activity context, long timeout){
if (context != null && timeout != 0){
init(context, DEFAULT_MESSAGE, timeout);
}
}
public static void now(Activity context, String message, long timeout){
if (context != null && !message.isEmpty() && timeout != 0){
init(context, message, timeout);
}
}
// Shutdown.kt
...// inside companion object
fun now(context: Activity?, message: String = DEFAULT_MESSAGE, timeout: Long = DEFAULT_TIME_OUT) {
context?.let { init(context, message, timeout) }
}
Calling the Kotlin implementation of now(context)
from a Kotlin class works without issues. Calling this Kotlin implementation from a Java class will show you the red squiggly line.
// SomeActivityClass.java
Shutdown.now(this); // no issues
//SomeActivityClass.kt
Shutdown.Companion.now(this); // Compile time error. Breaking change?? 😱
The compile-time error message read that three(3) arguments were expected but only one(1) was found. Which was strange because I provided default values to the other two arguments in Kotlin. Upon further investigation, I found that the Kotlin compiler wasn't generating the overloads for now(context)
so it was impossible to omit some arguments when calling from Java. In comes the @JvmOverloads
annotation. This annotation essentially instructs the Kotlin compiler to generate overloads for now(context)
.
// Shutdown.kt
@JvmOverloads
fun now(context: Activity?, message: String = DEFAULT_MESSAGE, timeout: Long = DEFAULT_TIMEOUT)
context?.let { init(it, message, timeout) }
}
Adding @JvmOverloads
resolves the compile-time error. And now we can call now(context)
from Java with one(1) or more arguments. But the Java usage still didn't feel natural. It didn't feel like a static method call. And that's what led me to @JvmStatic
. This annotation tells the Kotlin compiler to generate an extra static method for now(context)
.
// Shutdown.kt
@JvmOverloads
@JvmStatic
fun now(context: Activity?, message: String = DEFAULT_MESSAGE, timeout: Long = DEFAULT_TIMEOUT)
context?.let { init(it, message, timeout) }
}
// SomeActivity.java
Shutdown.now(this);
Now the Java client doesn't need to access Companion
in order to use now(context)
from Java. Order has finally been restored in our Java client after the Kotlin rewrite. No more breaking changes! You can find the Pull Request here, if interested.
Thanks to Eyram A. Agbe, Raymond(creator of Playlistor), Waheed Derby and Wilfred for taking the time to review this article.
Top comments (0)