Two Birds with One Stone

How to Support Kotlin and Java Simultaneously

If you ask most Android developers about Kotlin or follow the trends, it’s quite clear that Kotlin’s star is on the rise. 🌟

Kotlin already accounts for ~7.5% of Realm Platform users today and we continue to see that number grow month over month. We’re very excited about this at Realm because Kotlin’s concise nature makes it a great language choice for development with Realm.

Why is that? Because instead of doing this…

// To define objects
public class Dog extends RealmObject {
    private int age;
    private Person owner;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person getOwner() {
        return owner;
    }

    public void setOwner(Person owner) {
        this.owner = owner;
    }
}

// Query
RealmResults<Dog> puppies = realm.where(Dog.class)
        .lessThan("age", 2)
        .findAll();

// React to change
puppies.addChangeListener(new RealmChangeListener<RealmResults<Dog>>() {
    @Override
    public void onChange(RealmResults<Dog> puppies) {
        updateUI(puppies);
    }
});

// And mutate state
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Person me = new Person("me");
        me = realm.copyToRealm(me);
        dog.setOwner(me);
    }
});

Our users can do this…

// To define objects
open class Dog : RealmObject() {
    var age: Int = 0
    var owner: Person? = null
}

// Query
val puppies = realm.where<Dog>()
        .lessThan("age", 2)
        .findAllAsync()

// React to change
val listener = puppies.addChangeListener {
    _, puppies -> updateUI(puppies)
}

// And mutate state
realm.executeTransaction { realm ->
    dog.owner = realm.copyToRealm(Person("me"))
}

We strive to make the development experience as easy as possible for our users and Kotlin helps with this goal. Unfortunately, the Android ecosystem is not ready to switch over entirely to Kotlin yet.

The Road to Kotlin

Someday we’ll get to the 100% Kotlin code utopia but in the meanwhile, if you’re working with Kotlin, it means that you’re probably working with a mixed Kotlin and Java code base. Kotlin interoperates with Java out-of-the-box, but there are a few things you can do to make the development experience a bit nicer for both sides.

Get more development news like this

I will cover just a couple of things in this post which I found to be the most impactful for me and follow up with some other tips and observations in a later post.

First off, is the notion of nullability..

Nullability

Kotlin’s has nullability baked directly into the type system. This means that Kotlin code calling other Kotlin code is guaranteed to be null-safe, thereby eliminating NullPointerExceptions.

For example, take the following Kotlin API

class KotlinApi {
    fun method_returns_value() : String {
       return "some value"
    }

    fun method_might_return_null() : String? {
        return if(someCondition) "some value" else null
    }
}

The first function of this API, method_returns_value(), declares that it returns a non-null type String and indeed always returns a value. This Kotlin code will not compile if we even try to return null here, because the signature of the function promised it would return a non-null String object.

In the second function, we declare that we might return null as indicated by the String? return type. This allows us to return null given someCondition as we’ve done for this example.

Calling the API, probably works just as you would expect…

In the first case, Kotlin infers the result to be of type String and we can use it safely in our code without checking for null before dereferencing it.

val result = KotlinApi().method_returns_value()
println("string length is ${result.length}")

In the latter case, where a String? is returned. We need to use care when dereferencing it as you see in the code snippet below.

// Kotlin infers this to be a String?
val result = KotlinApi().method_might_return_null() 

// The String object must be safely dereferenced via a..

// Safe call
println("string length is ${result?.length}”)  

// Double Bang (which could result in an NPE if it truly is null)
println("string length is ${result!!.length}”)  

// Or you can take advantage of the Kotlin smart cast system, 
// which can infer the nullability of a val reference
// if you check the value in your code just before using it.
if(result != null) {
    println("string length is ${result.length}")
} 

The consuming Kotlin code will not compile if you try to treat the return value of method_might_return_null() as a non nullable value.

For example:

val result: String? = KotlinApi().method_might_return_null()  
// Compiler error: "Expecting String, found String?"

Indeed, Kotlin eliminates the Billion Dollar Mistake, but only for Kotlin code, calling other Kotlin code.

Let me explain by showing an example of the same API, but defined in Java.

final public class JavaApi {

    public String method_returns_value(){
        return "some value"
    }

    public String method_might_return_null(){
        return someCondition ? "some value" : null
    }
}

The first call will work just as we might expect.

val result = JavaApi().method_returns_value()
println("string length is ${result.length}”)

No surprises there, but what about the second call?
(and for this example, I’ll explicitly type the result to ensure there are no misunderstandings)

val result: String = KotlinApi().method_might_return_null()
println("string length is ${result.length}”)

Pop Quiz:

  • Q. Do you think this code will compile?
  • A. Well, yes actually, it will.

  • Q. What if a null is returned from method_might_return_null() at Runtime?
  • A. An NPE disguised as an IllegalStateException will be thrown.

  • Q. Wait, so this is by design?
  • A. Yup, these are known as Platform Types and it was a design decision meant to make it more practical to work with Java APIs from Kotlin. See the Kotlin documentation on Java Interoperability for more information.

🤔 So what can you do to address this and avoid Runtime NPE surprises?… Annotations FTW!

Nullable Annotations

Kotlin understands and respects nullable annotations from a variety of sources, including Jetbrains, Android, JSR-305 and others. You can find a complete listing of the annotations supported here. These annotations, when used in your Java classes, will give hints to the Kotlin compiler as to what can and cannot be null so that it can do a better job enforcing the rules on the Kotlin side. We’ll get into how to use these in a second. One thing I want to point out is that these annotation classes (from Jetbrains, Android, JSR-305, etc.) heavily overlap, so you typically only need to depend on one of them. Kotlin works equally well with all of them, but the version you choose will depend on your situation.

For example, if you’re building a 3rd party library (like Realm), JSR-305 is a nice option because it doesn’t tie you to Jetbrains or Android and you’re likely going to be sharing the cost of the additional transitive dependency (on JSR-305) with other 3rd party libraries. For example, many of the Square libraries, like Retrofit, already have a dependency on JSR-305.

On the other hand, you may just be working on an Android application that has a mixed Java and Kotlin codebase. In that case, go ahead and use the Android or Jetbrains ones. (Compiled Kotlin code actually uses the Jetbrains Nullable annotations).

Let’s see how we can use these annotations to keep the NPE monster at bay. Take a look at the following example:

@Nullable
public String method_might_return_null(){
    return someCondition ? "some value" : null
}

Notice that we’ve annotated the method with @Nullable. This provides a hint to the Kotlin compiler that this Java method can return null and should treat the return type as a String?. Now any code that tries to assign or use the return value of this method as a String, will not compile.

val someString: String = JavaApi().method_might_return_null()
// Compiler Error: inferred type is String? but String was expected

println("string length is ${someString.length}”)

This works for accepting parameters on the Java side as well.

@Nonnull
public List<Person> findPeopleMatchingCriteria(
        @Nonnull   String occupation,
        @Nullable  String name,

Kotlin users cannot pass null here for name

val myVal = SomeJavaApi().findPeopleMatchingCriteria(null, null)
// Compiler Error: Null can not be a value of a non-null type String

For more explanation on this as well as detail on how to enforce non-null as the default in your Java code, see this great write up by Jessie Wilson at Square.

okay be careful of null.. got it, anything else?… Let’s talk about extensions for a minute.

Extensions

Kotlin has added a great new feature called Extensions to the language. Extensions allow you to add functionality to existing classes without extending them. This means that you can even add methods onto final classes such as String and use them on existing objects of that class as if they were first class methods on the original object (with some caveats of course).

This has been commonplace for some time in many other languages, such as Swift and C#, but it is new for most Java developers.

I often hear questions about the caveats or how Kotlin extensions functions interoperate with Java code. To answer these questions, it helps to understand what actually happens when you define an extension.

Let’s say that I have a couple of String extension functions defined like you see below:

// Inside of a file named StringExtensions.kt

fun String.withManners() = this + " please. :-)"

fun String.withAuthority() : String {
   return this + " yesterday!!"
}

If you examine the Kotlin bytecode and then decompile back to Java, you’ll see that what actually gets created is something like this.

public final class StringExtensionsKt {
  @NotNull
  public static final String withManners(@NotNull String $receiver) {
     Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
     return $receiver + " please. :-)";
  }

  @NotNull
  public static final String withAuthority(@NotNull String $receiver) {
     Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
     return $receiver + " yesterday!!";
  }
}

Aha… As you can see, there is really no magic 🎩 here. We’re not somehow opening String (which is a final class) for extension and adding new functionality. Rather, we’re just adding some static utility methods.

However, since we’re adding them via extensions, Kotlin callers can invoke the utility methods as if they were part of the original type like this:

println("Get me some coffee".withManners())
println("I need those TPS reports".withAuthority()) 

The only real magic here is Kotlin’s syntactic sugar that allows you to pretend that these methods were part of the original class. With this information in hand, a lot of the questions go away, because you already know how static utility methods work in Java.

A few that often come up:

  • Q. Can extensions add state to an existing object?
  • A. No!

  • Q. Can you override an existing method like .toString() using extensions?
  • A. No! The real method on the object always wins. (shadows any extension).

  • Q. Can extensions access private member variables and methods?
  • A. Can Utils methods? Nope and neither can extension methods.

  • Q. Can I scope extensions? I mean couldn’t things get out of hand if one piece of code could add a method to all Strings across my entire project!?
  • A. Yes! Extensions must be imported just as any Utils class would for it to be in scope and available.

  • Q. Finally, how do I use the extension methods I define in Kotlin from my Java classes?
  • A. Just as you would Utils methods. For example:
    System.out.println(StringExtensionsKt.withManners("Get me some coffee"));
    System.out.println(StringExtensionsKt.withAuthority("I need those TPS reports"));
    

In this example, the class name StringExtensionsKt came from the name of the Kotlin file in which we defined our extension functions (StringExtensions.kt). This doesn’t feel very Java like, but we can fix that by adding @file:JvmName("StringUtils") in the first line of our extensions file.

Here is the full example and usage on both sides:

@file:JvmName("StringUtils")

package io.realm.examples.kotlin.extensions

fun String.withManners() = this + " please. :-)"

fun String.withAuthority() : String {
    return this + " yesterday!!!!"
}

Kotlin Usage

println("Get me some coffee".withManners())
println("I need those TPS reports".withAuthority())    

Java Usage

System.out.println(StringUtils.withManners("Get me some coffee"));
System.out.println(StringUtils.withAuthority("I need those TPS reports"));

Both of these output:

> Get me some coffee please. :-)
> I need those TPS reports yesterday!!!!

The take away I recommend here is to group your extensions into files pertaining to a specific object type (String extensions, Date extensions, etc.), and specify the @JvmName annotation so that your extensions look and feel like Java Utils classes to the rest of your Java code.

Conclusion

There are many other things to consider when interoperating between Kotlin and Java. In fact 2 sections of the Kotlin docs are dedicated to this topic.

If you’re working in a mixed Java / Kotlin code base, you should definitely check those out.

In another article, I’ll talk about using lambdas with Java Single Abstract Method (SAM) Interfaces as well the power of using reified types with inline functions.

Next Up: Modern-Day Development Strategies with Realm

General link arrow white

About the content

This content has been published here with the express permission of the author.


Eric Maxwell

Eric is an Product Engineer at Realm. He has spent over a decade architecting and developing software for various companies across different industries, including healthcare, insurance, library science, and private aviation. His current focus is training, mentoring, and mobile development. He has developed and taught courses on Java, Android and iOS. When he’s not working, he enjoys time with family, traveling and improv comedy.

4 design patterns for a RESTless mobile integration »

close