by Adam Arold
Tips for Writing a Library in Kotlin
Writing a library in Kotlin seems easy but it can get tricky if you want to support multiple platforms. In this article we’ll explore ways for dealing with this problem.
Why would I write a library in Kotlin? you might ask. If you have been using Kotlin for a while now you might think that you have everything within the Java ecosystem and it is not necessary to write anything new in Kotlin (apart from the application you are working on).
While Kotlin started out as a JVM language, now it can run on multiple platforms and you can even have isomorphic Kotlin in your project. As I have written about this before, Kotlin now can be used with Gradle in place of Groovy, on the frontend and also in your backend projects.
There is one thing which stands in the way though: the lack of mature multiplatform libraries. So if you want to help with this effort this article is for you.
So What is a Library?
Now that we decided to write a library it is useful to define what a library really is.
Note that this is just my opinion, I’m not an authority figure on the topic. Feel free to point out if I missed something or if you just simply disagree, in the comments section.
In my definition a library is
- Generic program code
- Written to perform a single task
- Bundled in a package
- And distributed to the public in this form
Apache Commons IO is a good example for this. We add it to our projects as a dependency, we use its functions, but it doesn’t try to change how we structure our project or write our code.
What is Not a Library?
A framework! A good example for this is Ruby on Rails. It is a framework which specifies for you how to code and how to structure your project. In this article we’re going to talk about libraries.
Things to Keep in Mind
Whenever you sit down to write a library there are some general guidelines which are applicable in any domain, not just for Kotlin ones.
Keep Your API Small
Giving an API to your users is similar to asking someone to hit a target with an arrow. The smaller the target, the harder it is to hit. This is true with libraries as well. The smaller the API you have, the easier it is to maintain, and you also minimize the chance that you accidentally expose something which you didn’t intend to. Take this API for example:
In the following examples we’re going to work on an imaginary library which exposes an API for handling UI widgets.
Here we expose the
MyComponent class which has a property to tell whether it is focused, it exposes its children, and also the draw surface which is used for rendering.
There are a lot of cases when you might have the feeling that you need to expose things because you think that it might be useful for you users. What actually happens most of the time is that you expose too much, your users start to rely on them, and later when you figure out that some of the internals of your library need to be refactored you have to break the API for your users if you want to fix it.
Let’s take a look at the same class with a smaller API:
So after evaluating our class it turns out that
isFocusedwas not needed at all and we only want our users to be able to either
requestthe focus. With this we preserved the same functionality but retained the ability to handle focus in any way we wish.
drawSurfaceis something which we take as a dependency but it is internal to our library so we shouldn’t allow the external world to tamper with it. It happens quite often that users start to use things which you exposed in ways you didn’t intend it to work so this helps with that problem.
- It also turns out that
attachTohad the same problem as
drawSurface: they are internal to the library and the users shouldn’t do rendering or component (re)attaching by hand.
Let’s take this a step further by introducing better abstractions.
Keep Your API Abstract and Clean
In the previous example we’ve cleaned up parts of our API and removed / hid some things which were not intended to be public. Now we’ll take a look at things we do expose:
After careful investigation we concluded that:
- We don’t really need the functionality
Lists provide for our
Iterablewill suffice. A good example for this is that if you expose a
Listof something which your users just want to iterate over with a for loop, you either lose the ability to construct items on the fly or make it much harder to implement. With an
Sequenceyou can do this easily. It also enables you to return an insanely high number of elements without filling up the memory.
- As it turns out
PixelGraphicsImplis a concrete implementation of
DrawSurfaceand it has some internal things which we don’t want to expose. It can become problematic if we expose implementation classes through the API and make it impossible to change the implementation behind the scenes without breaking your users’ code.
All of these lead us to the realization that to clean up this mess we should start to…
By taking a hard look at what we have, we can conclude that by exposing classes and concrete implementations through our API will lead to all sorts of problems, so using interfaces is a better approach overall:
This way we are free to implement
Component as we see fit. We can have any number of implementations for it if we want and it won’t affect our users. An important caveat for this is to only return abstract types from our factories:
This might seem obvious, but this is often overlooked.
The Modularization Problem
So we separated our API and our concrete implementations into interfaces and classes. The problem is that we can’t prevent our users from circumventing our clean API and using
MyComponent directly since Kotlin doesn’t have its own module system. What we can do is to separate our packages into
internal (or anything similar) and clearly state in the documentation that everything in
internal is subject to change:
This solution is not perfect, but it helps.
We’ve discussed a lot of things already, but we haven’t seen any Kotlin-specific tips yet, so let’s take a look at some.
It might be a case that you don’t use
companion objects in your project or you don’t have the need for them in some API classes. What’s important to point out here is that
companion objects enable your users to define extension functions on your classes which can be invoked without an instance. You can add an empty
and your users gain the ability to augment your interface as they see fit:
Take Extension Functions to the Next Level
Extension functions can also help you to create a more fluent API. Take a look at this example, where our user has a list of
In order to cancel them all they most probably write something like this:
But what if we provide this functionality out of the box?
cancelAll can be called on any
MutableList which holds
reified Functions Delegate Work
reified functions are very useful but they come with some caveats which are very frustrating. One of them is that we need to use
@PublishedApi if we want to access the internals of a class. For this reason it helps greatly if we simply delegate the work from them to functions which take
KClass objects as parameters so we get the utility of
reified functions without the problems:
Astute readers might spot the problem with this API. We’re not using interfaces! Unfortunately
interfaces don’t support
reified functions, but there is a solution which solves this problem:
reified Functions be Extension Functions
It is true that we can’t have
reified functions in an
but we can have
reified extension functions:
With this we get the best of both worlds, and usage stays the same:
The tips above are applicable on any Kotlin project but there is a special kind of project which needs more care than a regular one:
If you are working on a multiplatform library you need to write code which is idiomatic on all platforms. In the following section we’ll take a look at some tips which will help with this.
Use Properties Instead of Getters
Writing a getter (
getX) for a property is not idiomatic in Kotlin. On the other hand accessing fields in Java without getters is not idiomatic either! It turns out that Kotlin properties are implemented in a way that both sides will see an API they wish to see:
Hiding a Kotlin API
Sometimes you have functions which look weird for Java users. A good example for this is a lambda which has to return
Unit. Having to return
Unit for Java users is just weird. Luckily we have some ways to hide things from Java users:
This is nice but what if I want to…
Hide a Java API
Unfortunately there is no “official” way of hiding something from Kotlin users, but there is a hack which we can use:
internal functions are not visible for Kotlin users, but it is visible from Java. There are some caveats though:
- This is a hack!
- Interfaces can’t have
- We need to use
internalfunctions have a funky name when we try to access them from Java
If you work on a library chances are that you want to design it for extensibility so your users can add their custom things. Take this interface for example, which we want to make extensible:
The problem here is that from the Java side
calculateArea won’t have a default implementation only if we apply
@JvmDefault to it. The problem is that this will only work with Java 8+ which might not be available (on Android for example).
So what we can do is to create base classes.
If we want a base class which doesn’t implement all members we can provide
If they do, an
open class will do:
Just keep those functions
final which you don’t want your users to override.
Kotlin comes with some annotations which were designed to help with multiplatform development. One of them is
@JvmStatic which we can use to make members
static in the resulting Java bytecode, but it comes with some caveats:
Note that in the past this was not usable in common projects but they were modified to be optional so we can now put them on any class regardless of the platform.
One of those problems is that we can’t use it in
interfaces, not even on
companion objects defined in them.
A solution for this is to use
objects and have them delegate to the functions defined in an
Multiplatform SAM Problem
Suppose that we have an
interface which has a function which takes a lambda:
If we want to use this from the Java side it is awkward:
If we create a
Listener interface to be used as a parameter:
@FunctionalInterface would help here but we can’t use it in multiplatform common projects.
we won’t be able to use Kotlin lambdas here:
A solution for this problem is to keep the
Listener interface and provide Kotlin users with an extension function which accepts a lambda:
This way it will be idiomatic from both Java and Kotlin.
How to Deploy?
So we now have a nice library which is idiomatic, easy to maintain and behaves well in multiplatform environments. The question is how to deploy it? As of the time of writing, Maven Central and Bintray is hard to set up and the latter is not reliable. So what do we do?
As it turns out there is a free service which works out of the box and deployment is as easy as creating a tag on GitHub: JitPack.
My suggestion is to use this until official tooling arrives for Maven Central releases.
Note that I’m not affiliated with JitPack in any way.
We’ve explored some of the intricacies of library development with Kotlin. It might seem hard to do at first, but by following some simple guidelines it can become much easier with some practice. So armed with this knowledge…
Let’s go forth and kode on!
Thanks for reading! You can read more of my articles on my blog.