Crafting and publishing Kotlin Multiplatform library to Bintray
By: Yevhenii Kanivets
Tags: Android, Kotlin Multiplatform, iOS, Mobile, Kotlin

Kotlin Multiplatform is a great technology in the maturing period, which means that it's not always possible to find what you need for your project and you must craft it yourself. Are you ready for a challenge?

In this article, I'll share a step-by-step guide of creating and publishing of KMP library for JVM, Android and iOS targets. Target, if simply put, is just a type of device where you want your library to work.

Prerequisites

Codeforces WatchR is an open-source mobile client for Codeforces platform, where thousands of programmers compete in weekly algorithmic challenges. There are both, iOS and Android apps, which are available in stores.

Recently we've migrated both apps to Kotlin Multiplatform. In the process, we've just copy/pasted ReKotlin source code (doesn't support KMP at the moment) to KMP common module, which was the short-term solution.

But then we needed ReKotlin in another KMP project, so we decided to publish it as KMP library to Bintray. Now it can be included as a dependency in build.gradle file. Other developers can also benefit from our work.

Source code

All source code can be found in the official ReKamp (this is how we called the library) repo on GitHub. As you can see, all classes are located under src/commonMain folder, which means ReKotlin was 99% ready for KMP.

There is no platform dependent code and our changes are limited to a few tiny tweaks related to how Kotlin/Native compiler transforms Kotlin into Obj-C:

For some reason === didn't work as expected within Kotlin/Native, even though printed addressed were the same. Changed them to == .

didn't work as expected within Kotlin/Native, even though printed addressed were the same. Changed them to . All classes should extend other classes or Any . Otherwise you will have messed up generics, which are limited even without this problem.

. Otherwise you will have messed up generics, which are limited even without this problem. All methods, which begins with new or init are prefixed with do . Changed newState to onNewState to stay consistent between platforms.

Compilation

Configuration of your build happens in kotlin block where you specify your source sets and targets. jvm corresponds to any JVM-powered target and gives you an access to different Java-related APIs. android is subset of jvm , but allows you access Android-specific APIs.

It’s much more complicated for ios target. In many libraries listed in https://github.com/AAkira/Kotlin-Multiplatform-Libraries I've seen a shortcut for ios() preset of targets, which works good until you try to archive the iOS app, which uses the library, in XCode.

Architectures

If you inspect the default Valid Architectures field in your XCode project, you will see that it contains both arm32 (armv7, armv7s) and arm64 (arm64, arm64e) architectures.

But for some reason shortcut for ios() in KMP contains only x64 (simulator) and arm64 (devices) architectures. And when you try to archive the project, you will see following errors:

One of the solutions is getting rid of arm32 architectures in both your common KMP module and XCode project, but I'm not sure about implications of this choice. Comment if it's safe, please.

We've chosen another, more robust solution and decided to support iosArm32 in ReKamp. It can be done by explicitly including iosArm32 , iosArm64 and iosX64 targets in the build.gradle . Just make sure that their corresponding source sets depends on nativeMain .

Metadata

When you do ./gradlew build , outputs for each target are created. These are jar files for JVM targets and klib for Native ones. So far, so good.

The problem is that there is no target for common source set, so no outputs are generated. But Android Studio needs jar file, which is named your-library.jar to support highlighting in your common KMP module.

The solution is simple, you need to rename your-library-metadata.jar (and other metadata outputs) to your-library.jar . Here is the code, which makes exactly that + makes sure that other outputs are named properly.

Publishing

We've used com.jfrog.bintray plugin, which provides convenient methods for uploading your outputs and metadata to Bintray repository. To make sure that all outputs are correctly uploaded, we've added them to publications:

publish.gradle provides the most important information to publishing plugin: repository url, group, artifact and version.

pom.gradle is just another Gradle script, which is used to provide additional information about library' license, developer, etc in POM format.

All the values you see in Gradle files are gathered in gradle.properties :

Another 2 values are located in local.properties , which are private: bintrayUser and bintrayApiKey , which you can get by registering at https://bintray.com/ for free.

At this moment, you should be able to publish your library with ./gradlew bintrayUpload . You should see about 20 lines of Uploaded to ...

Usage

First of all, check that you have all outputs successfully uploaded to Bintray: Repository -> Package -> Files. You should see a few folders (common, mavenProject + one per target).

Now you can include these dependencies for targets you need like this:

Gradle cache

Half of the time I've spent fighting with Gradle cache, which was super persistent about keeping old versions of my library after changes without changing the version.

Here are a few advices on how to overcome caching mechanism:

update version of your library after every single change

of your library after every single change remove all obsolete versions of library from local Gradle cache (Project view -> External libraries -> your-library.jar (and all targets).

all of library from local Gradle cache (Project view -> External libraries -> your-library.jar (and all targets). once done remove all obsolete versions of library from Bintray

Bottom line

Creating and publishing Kotlin Multiplatform library was super challenging for the first time. It took me almost 2 weeks to get all things right in several rounds and a few broken projects.

I hope that this kind of step-by-step guide with working examples will help other developers to get their library published and working much faster.

Good luck!

Sources