Kotlin multiplatform technology is becoming more and more popular and the ecosystem is changing rapidly. Have you ever wondered about creating a multiplatform library? Now is a great time to start creating one, as there are a lot of open needs in the community and your involvement will be highly appreciated!
Of course, writing good code and providing useful API should be your top priority as a library author. But if you are publishing your library for the first time, there are some obstacles you’ll need to overcome. For example, as Bintray is shutting down in May 2021, you have to handle the Maven Central registration process and configure your library for publishing there.
This post will help you through the multiplatform library publishing routine giving you one less thing to worry about so that you can concentrate on the library itself 🧘🏼♀️
Plan
In the first part of the series we are going to create a multiplatform library that uses platform APIs with expect/actual mechanism, provides a simple API to the users, and supports three targets: Native (iOS or your host machine target, depending on your choice), JVM, and JavaScript. Such a library can be used in any KMM (Kotlin Multiplatform Mobile) or other Kotlin Multiplatform projects.
If you already have a project you want to publish, you can jump directly to the final step of this part – publishing the library to the local Maven repository to make sure that everything works correctly. In the next part, we will also discover published artifacts to get an understanding of the multiplatform publishing format. Finally, in the last part of the series, we will register a Sonatype account that is needed to publish your library to MavenCentral.
🤔 If you are feeling a little confused with all the new Maven terminology we are throwing around – it's okay! Here’s a useful twitter tip, which might help!
Next, we will set up the mechanism for publishing your project so you'll be able to configure, sign, and publish a library by calling a single Gradle task. Finally, we will publish your library in MavenCentral so that it will become available to developers all over the world!
There is lots to do, so let's get started!
ℹ️ Disclaimer: This tutorial is based on official documentation and other existing articles. They all provide useful and up-to-date instructions, but they don't fully cover the bumpy, winding road that is the publishing process. Here you will find a combination of the most relevant existing content and additional information, which should answer all your questions.
Setting up the environment
- If you want your library to support iOS or macOS targets, you need a macOS to build the native artifacts. But otherwise, you can build your library on any operating system.
- I will be demonstrating using IntelliJ IDEA (download the latest version here) with the latest Kotlin plugin, but you can use Android Studio as well. IntelliJ IDEA comes with a few useful Kotlin wizards, including a multiplatform library wizard.
- For creating the GPG keys that are needed to sign your library, we will use the GPG command-line tool. Download and install it manually, or with package management utilities, for example, brew:
brew install gpg
Alright, that’s enough introduction and preparing — let’s start!
Creating a multiplatform library
To publish something, we first need to build something! If you don't have a library yet, but you want to get some hands-on experience publishing one, let’s start by creating a sample library following the official tutorial, which describes the process of developing a multiplatform library from scratch.
In this tutorial, you will learn how to create a multiplatform library for JVM, JS, and Native platforms, write common tests for all the platforms, and publish the library to a local Maven repository. This library will convert raw data – strings and byte arrays – to Base64 format. To implement the conversion to the Base64 format on the different platforms, you will use the platform capabilities for JVM and JS, and write your own implementation for Native.
Supporting iOS target
This tutorial demonstrates building a library that supports native target equals your host target (so if you develop on macOS, then the resulting native artifact will be suitable for running on macOS). However, you can easily configure the supported targets set. For example, you can add the iOS target to make your library suitable for KMM projects. There are two ways to do this:
-
If you don’t need to support your host target and need only iOS, you could replace the host-dependent native target declaration that was generated by the project wizard with the
ios()
target declaration:
// Before kotlin { jvm { // … } js(IR) { // … } val hostOs = System.getProperty("os.name") val isMingwX64 = hostOs.startsWith("Windows") val nativeTarget = when { hostOs == "Mac OS X" -> macosX64("native") hostOs == "Linux" -> linuxX64("native") isMingwX64 -> mingwX64("native") else -> throw GradleException("Host OS is not supported in Kotlin/Native.") } } // After kotlin { jvm { // … } js(IR) { // … } ios() }
Don’t forget to also rename source set declarations and folder names according to the target name (
nativeMain
->iosMain
in our case). If you want to support both your host and iOS native targets and share code between them, you should manually configure the hierarchical project structure.
Supporting Android target
This tutorial uses jvm()
target declaration for JVM platforms support. The resulting artifact could be used in multiplatform projects that target any JVM platforms, including Android. If you want to make it possible to use your library directly from the Android project, or you need to use Android SDK to implement platform-specific features (for example, working with files stored on the device), use the android()
target declaration instead. To make it work, you need to connect the android-library
Gradle plugin and provide Android-specific information in the android
configuration block in the build.gradle.kts
:
android {
compileSdkVersion($compileSdkVersion)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") // Create AndroidManifest.xml and provide path to it
defaultConfig {
minSdkVersion($minSDKVersiom)
targetSdkVersion($targetSDKVersion)
}
}
Additionally, you need to specify the variant names in the Android target, as no artifacts of an Android library are published by default (see official docs for the details).
kotlin {
android {
publishLibraryVariants("release", "debug")
}
}
Complete the Create and publish a multiplatform library tutorial and configure the targets according to your needs. Here is the source code of the sample Base64 library with JVM, JS, and iOS support:
KaterinaPetrova / mpp-sample-lib
Sample Kotlin Multiplatform library (jvm + ios + js)
Now we push on to the next stage and publish our library!
Publishing your library to the local Maven repository
The only difference from developing a regular multiplatform module is using the maven-publish Gradle plugin. When used with maven-publish
, the Kotlin plugin automatically creates publications for each target that can be built on the current host (doc). Using the corresponding Gradle tasks provided by the maven-publish
plugin, you can deploy these publications to a remote or local repository.
If you already have a code you want to publish, then all you need is to apply maven-publish
and provide the library group and version information (see the last step of the tutorial).
plugins {
kotlin("multiplatform") version "1.4.31"
id("maven-publish")
}
group = "org.jetbrains.base64"
version = "1.0.0"
Running ./gradlew publishToMavenLocal
will upload all the needed artifacts to the local Maven repository, which is a directory on the local machine. You can check the result in this directory, which is usually located in /Users/<user_name>/.m2
directory, or by connecting your library to the sample multiplatform project, for example, the KMM project, which could be easily created with the KMM plugin for Android Studio project wizard. Add a dependency on your locally published library in the shared module build.gradle.kts
:
repositories {
mavenLocal()
}
kotlin {
android()
ios ()
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.katerinapetrova:mpp-sample-lib:1.0.0")
}
}
}
}
The best way to explore our new library structure is through the dependency
section in the project tree:
Wow! 😱 There is a lot of stuff in here and I know you must be curious about it! So before going public, let's spend a little time looking at how our library is structured — check out the next part of the series 👀
Top comments (4)
It seems android lib is missing in mpp-sample-lib project. That's quite confusing as in the article only steps are mentioned. Where can I find the full sample with a separate android support (not only jvm)?
Sample Github project just doesn't build.
I am curious why you chose to go with Sonatype instead of Github Packages for publishing?
For those who will encounter this question, I will try to answer this question.