Kotlin Multiplatform Mobile (KMM): more CI/CD tips and tricks

Introduction

Kotlin Multiplatform Mobile (KMM) is an SDK for multiplatform app development. It is easy to get started building KMM apps on Bitrise in two simple steps:

  • Import your KMM project as an Android project
  • Add the missing Bitrise Steps

In this blog, I’ll show how you can import your project manually or as an iOS project. I’ll also present a few Step combinations that can be used to test and deploy your KMM apps.

This blog is a follow up of Getting started with Kotlin Multiplatform Mobile (KMM), so if you’re completely new, start there.

TL;DR

The KMM projects used in this blog are available here:

The projects have working bitrise.yml files.

Background

Since the beginning of mobile, there have been many attempts to find the “holy grail“ of mobile development: to write code once and execute it on both mobile platforms. Modern multiplatform SDKs include React Native, Getting started with React Native apps and Flutter, Getting started with Flutter apps.

React Native Multi Platform Development

Code is written once and then it is run in the native ecosystems with the help of embedders; for example, see the Flutter architecture here: Flutter architectural overview. Although both SDKs have extension APIs, it is difficult to mix and match multiplatform code with purely native code.

Kotlin is well-liked for Android development. Moreover, after Kotlin Native was introduced, Kotlin code could also be used for iOS development thus becoming multiplatform.

Sometimes, developers want to implement certain modules or functionality in native code for performance reasons or because of the requirements of a specific integration. It is very easy to do this with KMM because the shared library is imported to the native projects as a standard Android dependency or an iOS framework respectively. It is up to the developer to decide how much multiplatform code they want to have in their projects.

Multiplatform CI/CD

Steps

KMM relies on the Gradle build tool to build and test the multiplatform code. Bitrise provides a couple of steps that can be used to accomplish the task: the Gradle Runner Step and the Android Build Step, which is built on the top of the gradle command.

The Gradle Runner Step
The Android Build Step

Project scanners

When a project is imported into Bitrise, a project scanner detects its type, then the user is asked for a handful of essential parameter values that are needed to configure and start the first build. The project type and the parameter values are used to create two default workflows: “primary”, which is used for testing, and “deploy”, which is used for deployment. The Bitrise project scanner configures your workflow and environment variables (Env Vars) automatically so that it will build successfully the first time with little or no setup required.

A KMM project can be added as either an iOS or Android app, or manually. There isn’t a Bitrise KMM project scanner yet.

A Manual Project Configuration Workflow

The Android project option has been explored here: Getting started with Kotlin Multiplatform Mobile.

The iOS option is similar in its motivation, that is, let the Bitrise project scanner generate default Steps for one of the platforms and add the Steps for the other platform manually. The scanning tool will suggest adding the project as an iOS project because it will detect the iOS part of the KMM project. Configuring your KMM project as an iOS project will result in selecting a relevant default Xcode stack. Moreover, iOS test and build Steps will be inserted and configured automatically.

From this point on, all the necessary Steps for testing, building and deploying the Android, iOS and shared parts of the project, must be inserted and configured manually.

The KMM Project

KMM Project Anatomy

A typical KMM project consists of three parts: an Android project, an iOS project, and a shared module. The shared module has the code that will be reused across platforms. It will be imported as a standard dependency on Android and as a framework on iOS. The shared module can also be using platform-specific flavours; for example, some iOS code can use ports of the standard iOS libraries.

The Native Projects Have the Shared Module as a Dependency

Vanilla project vs. CocoaPods

There are two types of KMM projects depending on the way in which the shared framework is integrated to the iOS project. The “vanilla” way would be to add the shared framework directly in the iOS project. However, if the iOS project is using CocoaPods, the shared framework will be added as a CocoaPod. This adds extra complexity when configuring the workflows.

KMM-specific assumptions

When a KMM project is added to Bitrise, one can make a few reasonable guesses about it.

  • It will have a shared module
  • The shared module will probably have multiplatform tests
  • For CocoaPods projects, the shared iOS framework will be a target in the Pods project of the Xcode workspace.
The Shared Framework Target in the Xcode Workspace

Typical Configurations

Tests

KMM projects contain Android, iOS, and Kotlin code, in the shared module. The purely native code can be tested in the same way as it is tested in native applications. However, the shared module is different. While the gradle test command would run standard Android tests, the cross-platform shared module tests use a different Gradle task, allTests . The iosTests Gradle task is a dependency of allTests but the test and allTests tasks are parts of different task hierarchies.

The allTests Gradle Task Hierarchy
An Excerpt Of the Standard test Task Hierarchy

In practice, this means that in order to run all tests, the gradle command must specify both the test and allTests tasks explicitly.

The AllTests Task Will Run Multiplatform Tests

It is worth mentioning that the shared Kotlin code is compiled to an iOS framework with Kotlin Native. However, shared code, including test code, can also have ported iOS libraries as dependencies.

Build and deploy

There is nothing unusual in a KMM Vanilla project when it comes to building and shipping the app. It is the combination of all Android and all iOS Steps that are usually needed for building and deployment in native projects. The Bitrise Android Build Step will build the shared module without the need of any extra configurations.

Building with CocoaPods is slightly more difficult. The CocoaPods variant of a typical KMM project is building and integrating the shared framework into the iOS project by using an Xcode build phase of a shared module target in the Pods Xcode project. Without building the shared Xcode target before installing the CocoaPods, attempting to build the iOS project will fail because the shared framework couldn’t be found.

Step Configuration for Building a CocoaPods KMM App
Building the Shared Framework with the xcodebuild Bitrise Step

Conclusion

The Kotlin Multiplatform Mobile SDK can be used for writing mobile apps in the Kotlin programming language. KMM differs from other platforms by allowing the developers to choose the proportion of multiplatform code that they want their projects to have. This is achieved by importing a shared library to two native projects: Android and iOS. The shared code is compiled by using the standard Android tools and it is also compiled to an iOS framework by using Kotlin Native.

While there are no complex challenges when building a KMM project on Bitrise, there are a couple of things to be aware of. One of them is testing the shared module because testing the multiplatform code is using a specific Gradle task which does not belong to the usual Gradle test task hierarchy. The other gotcha is the way in which the shared framework is added to the Xcode project with CocoaPods. It requires the shared framework to be built as a target in the Xcode project before CocoaPods is used to resolve the dependencies and create the workspace.

Both of these issues can be resolved by configuring and combining standard Bitrise Steps.

Get Started for free

Start building now, choose a plan later.

Sign Up

Get started for free

Start building now, choose a plan later.