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.
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.
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
- All classes should extend other classes or
Any. Otherwise you will have messed up generics, which are limited even without this problem.
- All methods, which begins with
initare prefixed with
onNewStateto stay consistent between platforms.
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.
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
iosX64 targets in the
build.gradle. Just make sure that their corresponding source sets depends on
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.
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
Another 2 values are located in
local.properties, which are private:
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 ...
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:
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
- remove all obsolete versions 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
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.