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:
- GitHub - atanas-bitrise/KmmTest: Kotlin Multiplatform Mobile test project.
- GitHub - atanas-bitrise/KmmVanilla: Kotlin Multiplatform Mobile without CocoaPods
- GitHub - atanas-bitrise/KaMPKit: KaMP Kit by Touchlab is a collection of code and tools designed to get your mobile team started quickly with Kotlin Multiplatform.
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.
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.
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.
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.
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.
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.
In practice, this means that in order to run all tests, the gradle command must specify both the test and allTests tasks explicitly.
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.
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.