Kotlinconf eric maxwell header

Building Libraries for Kotlin

Kotlin has a lot of great features that improve developer productivity and make the overall code base more stable. This all works great when your entire code base is written in Kotlin. However, while Kotlin adoption is growing by leaps and bounds, the fact is that many applications and libraries are still on Java and will be for some time. This means that if you’re a library provider like Realm that 100k+ developers rely on, you must provide an API that works well for both platforms.

In this talk, you’ll learn about some of the challenges faced and approaches taken by Realm to solve this problem and how you might apply these same approaches to your own libraries. You’ll also learn about some of the key Kotlin features used to simplify Realm Database development with Kotlin.


Introduction

I’m Eric Maxwell, and I’m a product engineer at Realm. My background is in Android and iOS development, so it’s exciting for me to see a different language, like Swift, on the Android platform.

This talk is about building libraries for Kotlin. It’s also about building apps for Kotlin that need to interoperate with both Kotlin and Java.

We saw at I/O 2017 that Google is now officially supporting Kotlin as a language on Android. Kotlin is about the bigger Java ecosystem, not just Android. I think that we’ll continue to see Kotlin grow across the entire Java ecosystem. In fact, for people using Realm, we’ve seen Kotlin adoption grow by 400 percent over the last year. We’re pretty excited about that.

Although the future may be Kotlin, in the present, we still have to support a lot of Java code, even if we write new apps and libraries in Kotlin. About 7.5 percent of Realm’s user base is Kotlin users, compared to 46 percent Java, across all the various languages. So for the foreseeable future, you’re going to be supporting both code bases. While Kotlin and Java are truly fully interoperable, there are many things that you can do to make the experience much nicer for both users on the Kotlin side and the Java side. Tonight, I’m going to go through what we’ve been building into our libraries to support both platforms and make it feel very natural to both sides.

This talk focuses on three main areas:

  1. The Kotlin Type System and the nullability that’s baked into the Type System, so that you can work nicely with Java.
  2. Extensions, and some fancy things that you can do with them, as well as a little bit about how to make extensions play nicely with both Java and Kotlin.
  3. Better ways that you can support Lambdas from Kotlin calling into Java APIs.

About Realm

Realm is a mobile database company. We have a free and open source mobile database that operates as a replacement for SQLite. The biggest difference is that it’s a full-fledged object database. You’re not writing SQL.

We also offer a mobile synchronization platform, and we have a new product we just launched, Realm Studio, which is a new cross-platform way to get a view into your data. This is a replacement tool for our previous object browser.

Kotlin Type System

Kotlin helps to simplify the API, which is something Realm likes to do, so our Java teams are very excited about it. I’ll give you a quick example of how you might define a persistent object, with Java, in Realm. Here’s a Java Dog class.

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;
	}
}

This would be a persistent Dog object, with some properties. It’s a fairly simple class, yet it takes an entire slide to show! That’s because of Java’s lovable, but verbose, nature, with all these getters and setters.

Also, Java loves anonymous inner classes for callbacks because it doesn’t support Lambdas natively:

RealmResults<Dog> puppies = realm.where(Dog.class)
	.lessThan("age", 2)
	.findAll();
puppies.addChangeListener(new RealmChangeListener<RealmResults<Dog>>() {
	@Override
	public void onChange(RealmResults<Dog> puppies) {
		updateUI(puppies);
	}
});

final Dog dog = new Dog();
realm.executeTransaction(new RealmTransaction() {
	@Override
	public void execute(Realm realm) {
		Person me = new Person("me");
		me = realm.copyToRealm(me);
		dog.setOwner(me);
	}
});

Get more development news like this

You can use retrolambda and some other tools to hide some of this. But Kotlin supports Lambdas, making a much more succinct way to use our API out of the box.

This is the Kotlin equivalent for the Java code above - notice how it uses less code to do the same thing:

open class Dog : RealmObject() {
	var age: Int = 0
	var owner: Person? = null
}
val puppies = realm.where<Dog>()
	.lessThan("age", 2)
	.findAllAsync()

val listener = puppies.addChangeListener {
	_, puppies -> updateUI(puppies)
}

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

Because properties are first-class citizens, there are no getters or setters for our users. Lambdas can be used in place of single abstract method interfaces, which I’ll cover here later. Very simple syntax can be used for transactions and reacting to changes. We’re pretty excited about it. It actually looks a lot more now like our Swift APIs did.

Now we will get into the Kotlin type system. First, I want to talk about nullability. Let’s say I had a very simple API class, called KotlinAPI, with two functions. The first one I’ve named method_returns_value so that it’s easy to see that it’s never going to return null. We know that it’s never going to return null in Kotlin because we’ve identified the return type as a string. And in Kotlin, a string is a string. It can never be null:

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

In contrast, method_might_return_null returns a nullable type. Sometimes, I call this “optional” because that’s the way Swift says it. So, if I say optional, instead of nullable, just know I mean nullable. Because I’m specifying a nullable string type here, this method can return null. That’s great on the Kotlin side because we avoid the billion dollar NullPointerException problem.

So the first method’s return value can be referenced safely in a println statement because there’s no way that it will throw a NullPointerException:

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

If I don’t type the second method’s return value, it’ll be inferred to be an optional string. But, in this case, if I even try to type it as a string, Kotlin’s compiler will not let us get away with this! It’s going to throw an error here, saying “Type mismatch” - “hey, I know that this method could return a null value.” So, of course, you can’t assign it to a string - the following line of code will generate a compiler error!

val result = KotlinAPI().method_might_return_null()
println("string length is ${result.length}")

You have to accept that it’s a string optional. Then you may want to not type it at all, and then safely dereference with a doublebang, e.g. result!!.length. The Kotlin site calls this the NullPointerException-lovers’ operator”.

Another option is “smart casting”. If you assign the result to a value - meaning it’s a final variable, it can’t change its reference - and then you check that it is not null, then you can use it as if it were an actual string type, and not a string optional: kotlin val result: String? = KotlinAPI().method_might_return_null() if (result != null) { println("string length is ${result.length}") }

But what about the same API, defined from the Java side? Let’s say I have a Java API that can’t express nullability in its type system because everything can be null. We have the same two methods, and the only difference is that they return Java strings. If I look at my same two usage examples, the first one works like you expect. You instantiate the API, you call the method, dereference the return value with result.length, and it just works. How about this second example?

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

Now I’m calling JavaAPI’s method which might return null. Do you think this will compile? Yes, it will because it’s returning platform types. It will also accept the optional version, val result: String? = ....

By default, the Kotlin compiler doesn’t know whether or not that method will return null. They could have made an assumption that every single time you call into a Java API it has to be treated as a string optional, but they deemed that as not really practical. If you were here for the workshop, Svetlana showed some really good examples of how difficult this could be, especially for return values that are collections like a list. The things in the list could be null, and the list itself could be null. There’d be a lot of things that you would have to check. So, it’s just not really practical. But, it also stinks. As a developer, you want to know if that API’s going to return null. One thing that I find troubling as a developer is that if I’m defining the result like this - val result = JavaAPI().method_might_return_null() - and I don’t know any better, I can’t even check after I assign it. I can’t check to see if it’s null! It’s going to throw a runtime IllegalStateException at the time of assignment here.

There’s a way to get away from this problem. If you’re building a Java API, you want to use some sort of nullability annotations. The Kotlin compiler accepts a variety of them. Probably the most popular three are at the top here:

Tool Annotation
JetBrains @Nullable and @NotNull org.jetbrains.annotations
Android com.android.annotations, android.support.annotations
JSR-305 javax.annotation
Others https://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations

In JetBrains, these annotations get added in when compiling. Android has some baked in. If you’re building a cross-platform library like we are, this only works for Android today. But in the future, we plan to expand that to work in the GVM in general. JSR-305 is a great option because it’s not tied to Kotlin or Android. If you’re using other libraries, like Retrofit or any of the Square libraries, you’re probably already depending on those libraries, because they do.

I’m going to show you a use case using JSR-305 because that’s what we’re using with Realm, and we’ve included it in our library. You just include a compile-time dependency on the annotation package like this:

dependencies {
	// ...
	compile 'com.google.code.findbugs:jsr305:3.0.2'
}

The annotations library contains a lot of annotations that you might find useful. But, specifically, for nullability, we’re going to cover these three:

  1. javax.annotation.Nullable
  2. javax.annotation.Nonnull
  3. javax.annotation.ParamatersAreNonnullByDefault*

There’s a little asterisk by that last one because it’s not quite fully supported. I’ll tell you what I mean by that in a second.

Once we’ve included them, any method that might return null can be marked as @Nullable. And then callers from the Kotlin side can no longer treat this, for example, as a string, because the method was marked with the @Nullable annotation. So the Kotlin compiler knows to not let you make that mistake, and it’ll treat it like the real type it should be.

This also helps you on the library side. Suppose I added the @Nonnull annotation on a method that might return null. Then I’ll get a warning about it, on the library side:

@Nonnull
public String method_returns_value() {
	return someCondition ? "some value" : null;
}

So it helps you build a better library, too.

It also helps when accepting parameters from the Kotlin side. For example, suppose I have a method that has six parameters being passed in, like this:

@Nonnull
public List<Person> findPeopleMatchingCriteria(
	@Nonnull String occupation,
	@Nonnull String title,
	@Nonnull String name,
	@Nonnull String otherProp1,
	@Nonnull String otherProp2,
	@Nullable String city
)

Suppose it was very important that the first five are never null, but the last one is nullable. If I annotated my API this way, Kotlin callers would not be able to pass a null in. They’d also get a compile-time exception for that.

It’s a little verbose to have all of these annotations everywhere. To help clean it up, you can enable Nonnull by default. Then you would annotate nullable things with that Nullable annotation. I’m going to cover the basic use case for doing that. If you’re interested in following up, there’s also a great detailed article about nullability by Jesse Wilson at Square.

To make parameters non-null by default, you add the annotation @ParametersAreNonNullByDefault. It can be added at the package level, the class level, and the method level, too. At Realm, we add it at the package level. So any of the public API classes, for example, in our I/O Realm instrumentation package, have non-null parameters by default. It looks like this:

package-info.java
@ParametersAreNonNullByDefault
package io.realm.instrumentation;

If you do that in your library, and your Kotlin users are using Kotlin version 1.1.4-3 and higher, it will work. But you have to enable JSR-305 annotations on the Kotlin compiler for the @ParametersAreNonNullByDefault annotation to work.

If you do that, and your client has done their job, then they’ll still get a compiler error here. If they don’t do their job, then they’ll at least get a warning in Android Studio. But, since there’s no way to guarantee that they’ll do that every time, or you might have a Java caller instead of a Kotlin one, it’s still a good idea in your APIs, and on the Java side, to do some sort of null checking. Maybe throw in an IllegalArgumentException or something. It just helps callers of your API to encounter fewer NullPointerExceptions, right? The billion dollar problem becomes more like a million dollar problem.

Here’s something that’s more specific to Realm. If you’re using Realm, and you’ve marked a field as required, it’s because you want to make sure that there’s a value when that object is saved to disc. So, if I had a Java Person with three properties - ID, name and phone number = with the first two being required, you couldn’t possibly save a Java Person to your Realm database without supplying a required value:

@RealmClass
public class JavaPerson implements RealmModel {
	@PrimaryKey
	@Required
	private String id;
	@Required
	private String name;
	private String phoneNumber;
}

This is redundant with Kotlin because it has this baked into the type system. This is an example of the same class in Kotlin:

@RealmClass
open class KotlinPerson : RealmModel {
	@PrimaryKey
	@Required
	var id: String = ""
	@Required
	var name: String = ""
	var phoneNumber: String? = null
}

In fact, if you compile the Kotlin class and then decompile it back to Java, it actually adds the @NotNull annotations from the JetBrains side. So, the @Required annotations are redundant, and Realm no longer requires them. If you’re using Kotlin, we look at the @Nonnull annotations. So you can build out your Kotlin models now just using the type system to infer the nullability.

Kotlin Extensions

One way that objects have traditionally been created in Realm is to ask Realm to execute a transaction using an anonymous inner class, like this:

realm.executeTransaction(new Realm.Transaction() {
	@Override
	public void execute(Realm realm) {
		Dog dog = realm.createObject(Dog.class);
	}
})

Kotlin makes that a lot easier because of a nice Lambda syntax. You can pass in exactly what you want to have happen in this transaction, very succinctly, in one line:

realm.executeTransaction {
	val dog = realm.createObject(Dog::class.java)
}

You can also add inline functions with reified type parameters so that your users don’t actually have to pass in the class object here. You can infer what that class is by having them parameterize the call, like this:

realm.executeTransaction {
	val dog = realm.createObject<Dog>()
}

Here’s the function that makes that possible:

inline fun <reified T : RealmModel> Realm.createObject(): T {
	return this.createObject(T::class.java)
}

It’s an inline function, and it’s reifying the type. That’s a big deal for Java, which has type erasure in its generics. It means that when you define a method in Java, or a function, that’s not in-line reified, the runtime has no idea what this T actually is. It just knows the bound of it. So to be able to do this is a big deal. And the reason that it works, is for every call to this inline function, the code is actually copied into the call site. It’s not actually calling another function. And because it’s copied into the call site, and we’re using this reified keyword, it uses the actual class.

I’ll give you a concrete example with this. This is actual code that I decompiled from these two statements:

realm.executeTransaction {
	val dog = realm.createObject<Dog>()
}

inline fun <reified T : RealmModel> Realm.createObject(): T {
	return this.createObject(T::class.java)
}

// Generated Java - no reflection!
realm.executeTransaction((Transaction)(new Transaction() {
	public final void execute(Realm realm) {
		Realm $receiver$iv = realm;
		RealmModel var10000 = $receiver$iv.createObject(Dog.class);
		Intrinsics.checkExpressionValueIsNotNull(
			var10000, "this.createObject(T::class.java)");
		Dog var2 = (Dog)var10000;
	}
}));

The method at the top asks Realm to execute the transaction. Inside of that transaction, I ask Realm to createObject. That’s calling my extension method, Realm.createobject that allows the type to be parameterized here. Then it, in turn, calls our Realm’s createObject, and passes the actual class. Inside this call site you have your anonymous inner class. And inside of that method, it gets a handle on this Realm here, and asks it to createObject. Notice that it’s actually passing Dog class. So it’s not using any sort of reflection. There’s no real magic here, other than code being copied. I think that’s pretty cool. And we’ve actually used that for creating objects, deleting objects, and for our queries, so that you can just say something like Realm.where and parameterize it as a type.

The other thing I wanted to talk about a bit is extensions versus utility classes. If you’re coming from the Java side, you may have a class that implements an interface, such as RealmModel. There’s no way to add behavior onto interfaces directly. So you end up doing a lot of static utils methods. For example, we have this way to react to changes that happen to our data model with Realm objects using .addChangeListener. That’s a little bit clunky, especially if you’re using a nice, succinct language like Kotlin. So we’re starting to add extensions instead. We have this .addChangeListener method available on any Realm model. And then it, in turn, calls the extension method.

For example, suppose I’ve added a couple of extension methods onto a string. The first one’s withManners. It takes the string and suffixes it with “please, smiley face”, because that’s very polite. The next method is withAuthority. That method suffixes the string with “yesterday!!!!”. Calling that works just as if it were a method. We know that it’s really a static utils method, but it operates just as if it were a method on the original string. If you look at the Java usage, then you can see that it has this funky naming. It’s still a utils method, but it’s StringExtensionsKT.withManners. The reason for that is the file name. This came from a file named StringExtensions.kt. So you have to call it with that. However, you can control the class name that gets generated for this. You can use this @JVMName annotation and apply it to your file, and then the generated class name will be StringUtils. And then I can use it just as if were StringUtils. It feels a lot more natural to your Java developers, as well as to your Kotlin developers.

Higher Order Functions in Kotlin

Next, I want to talk about using higher order functions. There’s a lot of power in Kotlin. You can make your APIs simpler to use. Suppose you have some library that you’re building, even internally, for use in your Kotlin side. Don’t accept the way the library was written. Because it was written and provided, originally, for other Java code to call it. If you think there’s a simpler way to access it from the Kotlin side, there probably is. And let me show you, by example.


// Extension
fun <T> Realm.callInTransaction(action: Realm.() -> T): T {
    val ref = AtomicReference<T>()
    executeTransaction { ref.set(action(it)) }
    return ref.get()
}

val (person, dog) = realm.callInTransaction {
	val pet = createObject<Dog>().apply {
		name = "Tony"
		age = 2
	}
	val person = createObject<Person>(101).apply {
		name = "Andy Kotlin"
		age = pet
	}
	Pair(person, pet)
}
// Andy Kotlin has a 2-year old dog named Tony
println("${person.name} has a ${dog.age} year old dog named ${dog.name}")

Here, we have a function that has been added as an extension function to a Realm instance. And inside there, you can pass a Lambda, which is operating within a receiver type of a Realm that’s also in a transaction. I said a lot there. But what you can do in this Lambda is operate within your realm as if you’re in a transaction. So, inside here, I can create a couple objects, maybe I create a pet of type Dog. I’m using the Kotlin apply function to set properties directly on this Dog object that I just created. Same thing with person. I’m specifying the primary key (101), and then setting some properties inside. Then I’m returning a Pair and using Kotlin’s destructuring syntax to create a person val and a dog val, all from this code. This Pair is dynamic. I can return anything I want from this, and it will just work. And then I can use it outside of the block. Let me show you how that works.

It’s actually a three line function, but it’s doing a lot of coding. Again, we’re defining this callInTransaction function. It takes an action, which is a Lambda itself. But it’s a Lambda that operates on a receiver type of Realm, and returns a T. Now, we don’t know what T is, and we don’t really care in this method. We just know that it returns a T, and that’s what gives the flexibility for it to return whatever it wants. Then, inside of this function, we create this atomic reference value, because we want to pass a value across a class in here. And again, we don’t know what type it is, but it’s of type T. It’ll be whatever this thing returns. And then, we call executeTransaction, which is a member function on Realm. Inside there, we invoke the action, which operates on a receiver type of Realm. In this case, it’s it, which is now a transactional realm. And the result of that, whatever that is, gets set into this reference, and then returned by it. That’s a lot going on here. There’s certainly a lot of rope you can hang yourself with, if you get junior engineers in there who just aren’t sure what they’re doing. But if done succinctly in the right way, you can make some beautiful APIs with Kotlin. So I suggest that you think about this and experiment to see how you can make your APIs better for Kotlin side.

Lambda Support in Kotlin

Everybody knows Runnable in Java, right? It’s an interface that’s used for many things, e.g. the Thread class. This is called a single abstract method interface. It’s an interface with a single abstract method that must be implemented, and generally used as a callback, or as a contract. For example, as a contractor thread, when you pass this in, it’s going to call run, which it knows is there because this is a Runnable interface. One of the cool things that you can do on the Kotlin side is pass a Lambda for that, as opposed to the anonymous inner class. So, for example, let’s say I defined a runnableBlock here. Now, this is a Lambda. It’s not at all a Runnable, yet. But, if I pass it into this Thread class’s constructor, which takes a Runnable as one of the constructor overloads, it will actually coerce this. It’ll say that the Lambda that is passed in could fit the signature of this Runnable, so I’ll use it as that.

// Java side
public interface Runnable {
	void run();
}

// Kotlin side
val runnableBlock = {
	// runnable code to execute...
}

val t = Thread(runnableBlock)
t.run();

In fact, it gets better than that. You can inline it, so I can say new Thread, and I can pass in that block directly:

val t = Thread({
	// Runnable code here
})
t.run();

You can actually drop the parentheses completely because it’s the only argument to the constructor here:

val t = Thread {
	// Runnable code here
}
t.run();

If it’s the last parameter, you can always pass it outside of the parentheses. But, only if it’s the last. This is more of an Android specific example. Android has a Handler class with a Java method of postDelayed which takes a Runnable, and a delay in milliseconds:

public final boolean postDelayed(Runnable r, long delayMillis) {
	...
}
...
// Kotlin side
val delay: Long = 1000
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
	// do something
}, delay)

If you were building this yourself, and you just reversed those APIs, you could then pass that Lambda outside of there, like this:

public final boolean postDelayed(long delayMillis, Runnable r) {
	...
}
...
// Kotlin side
val delay: Long = 1000
val handler = Handler(Looper.getMainLooper())
handler.postDelayed(delay) {
	// do something
}

The takeaway here is if you’re building these libraries, try to make an overload so that your anonymous inner class - your SAM interface - comes last because it makes it much nicer from your Kotlin side.

Conclusion

That’s everything we’ve worked on so far. We’re also looking at clever ways to use coroutines within our APIs because we have a lot of callbacks. It would be cool if we could use coroutines in a creative way there.

Most of what I’ve shown here is available in our GitHub repo, under our Realm Java project. There’s an example subdirectory inside there with a Kotlin example.

I wanted to leave with a few acknowledgements. First off, I wanted to thank Makoto “Zaki” Yamazaki on our Java team. Zaki is responsible for 99 percent of the things that we’ve done within our APIs that support Kotlin. Also, I want to thank our tech lead on the Android side, Christian Melchior. The guy’s a machine, I don’t think he sleeps. When he’s not working, building, and leading our team, he’s out there in the community helping out, so thanks so much to him. And we have a lot of great community supporters. Specifically, these three guys, on the Kotlin inter-ops side, have really contributed a lot and been a big help: Gabor “Zhuinden” Varadi Levi Bostian Heinrich Reimer

Feel free to reach me - You can always find me at [email protected], and I’m on Twitter, @emmax.

Next Up: Kotlin in Depth #10: What Kotlin Does to Your Java

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