Multi-platform libraries built with Kotlin Multiplatform (KMP)
Tags: kotlin, webdev, javascript, swift
Published: 2024-03-14 18:10:58

## Introduction

**Are you building libraries?** When I create services, I always try to extract domains as libraries whenever possible. Also, if the library is generally useful, I want to release it as open-source software (OSS). So, what kind of technology stack would you use at that time? That's where [Kotlin Multiplatform] comes in. Kotlin Multiplatform is a framework that allows you to create things for various platforms using Kotlin. Writing libraries in the modern language Kotlin allows them to be used on various platforms, so you can share code between server and frontend, and it should be very attractive to be able to run in various languages as OSS.

## Kotlin Multiplatform

{% embed https://kotlinlang.org/docs/multiplatform.html %}

With [Kotlin Multiplatform], you can build mainly three types of builds for different environments:

- Kotlin/JVM
  - For JVM environments such as Java and Kotlin
- Kotlin/JS
  - For JavaScript environments such as browsers
- Kotlin/Native
  - For native execution environments such as iOS, MacOS, and Windows

In this way, with Kotlin Multiplatform, you can create libraries that work in the above environments. However, Kotlin Multiplatform can only run libraries made for Kotlin Multiplatform, and for example, you cannot use a library written in Java with Kotlin Multiplatform. Therefore, there are some **parts where implementation is not straightforward, such as when the necessary functionality for writing a library is not available and you have to create it yourself.** Here, I will introduce some implementation points (I will add more points if necessary).

## Implementation Challenges

### Desired functionality library does not exist!

**There's nothing you can do.** However, Kotlin's official provides several libraries, so the scope that can be achieved using them is by no means small. Also, [AAkira/Kotlin-Multiplatform-Libraries](https://github.com/AAkira/Kotlin-Multiplatform-Libraries) introduces several famous libraries created with Kotlin Multiplatform, which can be helpful. However, it can be quite disappointing when certain environments are not supported. **But let's think of it as an opportunity! Let's become the first person to implement it!**

### There's absolutely no documentation!

Really, even if you look at the official documentation, you might not understand how to implement it. **Well, to be precise, you can implement it, but you don't know how to deploy it.** Or, you may not understand how to use Gradle, the build tool. Since there's no specific place to look, you'll have to try your best to search online, so please refer to the project I created.

{% embed https://github.com/uakihir0/kmisskey %}

This library is a client library for [Misskey](https://misskey-hub.net/en/), a Japanese social networking service. You can easily access Misskey's API using this library. The code above is Kotlin Multiplatform code, which can be run in Kotlin/JVM environments. For Kotlin/Native's iOS and MacOS, you can use [kmisskey-cocoapods](https://github.com/uakihir0/kmisskey-cocoapods) built from the [kmisskey] library and install it via Cocoapods. For Kotlin/JS targeting JavaScript, you can use [kmisskey.js](https://github.com/uakihir0/kmisskey.js) and install it via npm. The build methods for each are described in Github Actions, so please check them along with Gradle.

### (Reference) How can the library be used?

The [kmisskey] library created with Kotlin Multiplatform can be executed with the following code. One of the charms of Kotlin Multiplatform is that you can run similar code on almost any platform.

#### Kotlin/JVM

```kotlin
// Kotlin
import work.socialhub.kmisskey.KmisskeyFactory
import work.socialhub.kmisskey.api.request.i.IRequest
...

val misskey = KmisskeyFactory.instance(
    "https://misskey.io",
    "CLIENT_SECRET",
    "ACCESS_TOKEN",
)

val response = misskey.accounts().i(IRequest())
println(response.json)
```

#### Kotlin/Native (iOS/MacOS)

```swift
// Swift
import kmisskey
...

let misskey = KmisskeyFactory().instance(
  uri: "https://misskey.io",
  clientSecret: "CLIENT_SECRET",
  userAccessToken: "ACCESS_TOKEN"
)

let response = misskey.accounts().i(request: CoreIRequest())
print(response.json)
```

#### Kotlin/JS (JavaScript)

```javascript
// TypeScript
import kmisskey from "kmisskey-js";
import KmisskeyFactory = kmisskey.work.socialhub.kmisskey.KmisskeyFactory;
import IRequest = kmisskey.work.socialhub.kmisskey.api.request.i.IRequest;
...

const factory = new KmisskeyFactory();
const misskey = factory.instanceUserAccessToken(
  "https://misskey.io",
  "CLIENT_SECRET",
  "ACCESS_TOKEN",
);
misskey
  .accounts()
  .me(new IRequest())
  .then((res) => {
    console.log(res);
  });
```

### Coroutine behavior in Kotlin/JS

There are some challenging aspects to coroutines in Kotlin/JS for browsers. **The biggest challenge is that the `runBlocking` function cannot be used in Kotlin/JS browser builds.** `runBlocking` roughly converts asynchronous code execution to synchronous execution, and within `runBlocking`, you can execute asynchronous code (suspend functions). However, this conversion to synchronous execution cannot be done for browser environments that operate on a single thread, and writing `runBlocking` in the code will result in an error.

So, you might think, why not just provide asynchronous functions (suspend functions) as a library? However, **suspend functions do not support the `@JsExport` annotation, which explicitly exports them in JavaScript, and these functions cannot be represented in JavaScript.** Also, suspend functions are output in a slightly cumbersome form when used in Java, so it may be simpler to provide them as blocking functions in the library. (This is a personal opinion.)

**On the other hand, Kotlin/JS provides the `Promise` class for asynchronous execution, which corresponds to JavaScript's `Promise`, and by returning this model, you can return asynchronous processing in synchronous functions.** Since this `Promise` class is from JavaScript, you can proceed with processing as usual using functions like `then`.

However, how can the same code return data itself in Kotlin/JS and return a `Promise` in Kotlin/JS? You might wonder. In Kotlin/JS, there is a special type called `dynamic`, which, like JavaScript, accepts any type without causing an error. **Using this `dynamic` type, let's rewrite something equivalent to `runBlocking` for Kotlin/JS.**

```kotlin
// commonMain
expect fun <T> runBlocking(block: suspend CoroutineScope.() -> T): T

// jsMain
actual fun <T> runBlocking(block: suspend CoroutineScope.() -> T): dynamic {
    @Suppress("OPT_IN_USAGE")
    return GlobalScope.promise { block() }
}
```

At this point, it's important to note that the `Promise` returned with `dynamic` is not the original type `T`, so if you touch the value of this `Promise` incorrectly, it will result in a runtime

 error! Therefore, make sure that functions returning this `Promise` always return the value itself in the library. Also, in Kotlin/JS, since TypeScript type definitions are included, you need to rewrite the definition to a type wrapped with `Promise`.

## Conclusion

Writing libraries with [Kotlin Multiplatform] is quite fun and great! When it comes to Kotlin Multiplatform, it often focuses on use cases like Kotlin Multiplatform Mobile, where iOS and Android apps are packaged together, and it's not often seen to create shared libraries. So please challenge yourself! And if you release that library as OSS, please let me know!

[Kotlin Multiplatform]: https://kotlinlang.org/docs/multiplatform.html
[kmisskey]: https://github.com/uakihir0/kmisskey