Get Started With Kotlin Multiplatform

1. Before you begin

Prerequisites

  • Knowledge of how to build Jetpack Compose apps.
  • Experience with Kotlin.
  • Basic understanding of Swift syntax.

What you need

What you will learn

  • Understand the basics of Kotlin Multiplatform.
  • How to share code across platforms.
  • How to connect the shared code on Android and iOS.

2. Introduction to Kotlin Multiplatform

Kotlin Multiplatform (KMP) allows you to write code once and share it across multiple target platforms, such as Android, iOS, Web, and Desktop. By leveraging KMP, you can minimize code duplication, maintain consistency, and significantly reduce development time and effort.

KMP doesn't dictate how much or what parts of your codebase you need to share. It is up to you to decide which parts of the code are worth sharing.

Deciding on what to share

This shared code enables you to maintain consistency and reduce duplication across platforms. Many mobile teams start by sharing a discrete set of business logic (e.g., database access, network access, etc.) and the associated tests, and then sharing additional code over time.

Many Android Jetpack libraries have support for KMP. Jetpack libraries that have been made multiplatform offer multiple tiers of support depending on the target platform. For the full list of libraries and their support levels, see the documentation.

For example, one of the supported libraries, the Room database library, supports both Android, iOS & Desktop. This allows you to port the database creation and related database logic to a common KMP shared module while preserving the other native code on both platforms.

A possible next step after migrating the database would be to share other domain logic. Then, you may also consider using the ViewModel multiplatform library from Android Jetpack.

How to write platform-specific code

Kotlin Multiplatform introduces new techniques for implementing platform-specific functionality.

Expect and actual declarations

The expect actual Kotlin language feature is designed to support Kotlin multiplatform codebase with full IDE support.

This approach is ideal when the platform-specific behavior can be encapsulated in a single function or class. It's a flexible and powerful mechanism. For example, a common expect class can have platform-specific actual counterparts with more open visibility modifiers, additional super types, or different parameter types or modifiers. These kinds of variations wouldn't be possible with a strict interface API. Moreover, expect actual is statically resolved, meaning the platform-specific implementation is enforced at compile time.

Interface and Implementations in Kotlin

If both platforms need to follow similar APIs but with different implementations, you can define an interface in shared code as an alternative to expected and actual declarations.This approach enables you to use different testing implementations, or switch to a different implementation at runtime.

Additionally, interfaces require no Kotlin-specific knowledge, making this option approachable for developers familiar with interfaces in other languages.

Interface in common shared code, implementation in native code (Android or Swift).

In some cases you need to write code that isn't available from KMP code. In this situation you can define an interface in shared code, implement it for Android in Kotlin, and provide an iOS counterpart in Swift. Typically, the native implementations are then injected into the shared code, either by dependency injection or directly. This strategy allows for a customized experience on each platform while maintaining a common interface for shared code.

3. Get set up

To get started, follow these steps:

  1. Clone the GitHub repository:
$ git clone https://github.com/android/codelab-android-kmp.git

Alternatively, you can download the repository as a zip file:

  1. In Android Studio, open the get-started project, which contains the following branches:
  • main: Contains the starter code for this project, where you make changes to complete the codelab.
  • end: Contains the solution code for this codelab.

This codelab begins with the main branch. You can follow the codelab step-by-step at your own pace.

  1. If you want to see the solution code, run this command:
$ git clone -b end https://github.com/android/codelab-android-kmp.git

Alternatively, you can download the solution code:

The sample app

This codebase contains both an Android app built with Jetpack Compose and an iOS app built with SwiftUI. The Android project is located in the androidApp/ folder, whereas the iOS project is located in the iosApp/ folder that also contains the KMPGetStartedCodelab.xcodeproj to run with Xcode.

Install Xcode

To install Xcode, you need an Apple account. Most likely you will have one if you use an Apple device. You'll also need to install a simulator to be able to run the app.

This codelab was tested with Xcode 16.3. If you use any other Xcode version, and if you're running into issues, then we recommend downloading the exact version as mentioned in this codelab.

Open the Xcode project from Android Studio

Once Xcode is installed, you should make sure that you can run the iOS app.

You can open the iOS project directly from Android Studio.

  1. Switch the Project pane to use the Project View4f1a2c2ad988334c.png
  2. Find the KmpGetStartedCodelab.xcodeproj file in [root]/iosApp/ folder 1357ffb58fe05180.png
  3. Right-click the file and select Open In and Open in Associated Application. This will open the iOS app in Xcode. f93dee415dfd40e9.png
  4. Run the project in Xcode by clicking ⌘+R or navigating to Product menu and select Run

fff7f322c13ccdc0.png.

4. Add a KMP module

To add KMP support to your project, first create a shared module for the code that will be reused across platforms (Android, iOS).

Android Studio provides a way to add a Kotlin Multiplatform module using its KMP Shared module template.

To create the KMP module, in Android Studio:

  1. Navigate to File > New > New Module > Kotlin Multiplatform Shared Module
  2. Change the package to com.example.kmp.shared
  3. Click Finish4ef605dcc1fe6301.png
  4. Once the module creation completes and Gradle finishes syncing, a new shared module will appear in the project. To see the view shown below, you may need to switch from Android View to Project View.

eb58eea4e534dab2.png

The shared module generated by the KMP Shared module template includes some basic placeholder functions and tests. These placeholders ensure that the module compiles and runs successfully from the start.

Important: Keep in mind the difference between the iosApp folder and the iosMain folder. The iosApp folder contains the standalone iOS app code whereas the iosMain is part of the KMP shared module that you just added. iosApp contains Swift code while iosMain contains iOS platform-specific KMP code.

First, you need to link the new shared module as a dependency in the :androidApp Gradle module to enable the app to use the shared code:

  1. Open the androidApp/build.gradle.kts file
  2. Add the shared module dependency in the dependencies block as follows
dependencies {
    ...
    implementation(projects.shared)
}
  1. Sync the project with its Gradle files c4a6ca72cf444e6e.png

Verify code access to the shared module

To verify that the Android app can access code from the shared module, we'll make a simple update to the app.

  1. In the KMPGetStartedCodelab project, open the MainActivity file at androidApp/src/main/java/com/example/kmp/getstarted/android/MainActivity.kt
  2. Modify the content Text composable to include the platform() information in the displayed string.
Text(
  "Hello ${platform()}",
)
  1. Click ⌥(option)+return on your keyboard and select Import function 'platform'da301d17884eaef9.png
  2. Build and run the app on an Android device or emulator.

This update checks whether the app can call the platform() function from the shared module, which should return "Android" when running on the Android platform.

9828afe63d7cd9da.png

5. Set up the shared module to the iOS app

Swift can't use Kotlin modules directly like the Android apps do, and requires a compiled binary framework (XCFramework bundle) to be produced. An XCFramework bundle is a binary package that includes the frameworks and libraries necessary to build for multiple Apple platforms.

How the shared library is distributed

The new module template in Android Studio already configured the shared module to produce a framework for each of the iOS architectures. You can find the following code in the shared module's build.gradle.kts file.

val xcfName = "sharedKit"

iosX64 {
  binaries.framework {
    baseName = xcfName
  }
}

iosArm64 {
  binaries.framework {
    baseName = xcfName
  }
}

iosSimulatorArm64 {
  binaries.framework {
    baseName = xcfName
  }
}

This step involves setting up Xcode to run a script to generate the Kotlin framework, and calling the platform() function in the iOS app.

For consuming the shared library, you need to connect the Kotlin framework to the iOS project with the following steps:

  1. Open the iOS project (the iosApp directory mentioned previously) in Xcode and open the project settings by double-clicking the project name in the project navigator 94047b06db4a3b6f.png
  2. On the Build Phases tab of the project settings, click + and select New Run Script Phase. This adds a new "Run script" phase after all other phases. d4907a9cb0a5ac6e.png
  3. Double-click the Run Script title to rename it. Change the default Run Script name to Compile Kotlin Framework so it's understandable what this phase does.
  4. Unfold the build phase, and in the text field below Shell, enter the script code:
cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode

7b6a393e44ddbe60.png

  1. Drag the Compile Kotlin Framework phase before the Compile Sources phase. 27dbe2cf958c986f.png
  2. Build the project in Xcode by clicking ⌘+B or navigating to Product menu and select Build. Note that the build progress is shown on top of Xcode. bb0f9cb0f96d1f89.png

If everything is set up correctly, the project will successfully build.

bb9b12d5f6ad0bac.png

Setting the run script build phase this way allows you to compile your iOS project from Xcode without the need to switch to a different tool to compile the shared module.

Verify code access to the shared module

To verify that the iOS app can successfully access code from the shared module, you'll make the same simple update to the app that you made for the Android app.

  1. In the iOS project, in Xcode, open the ContentView.swift file at: Sources/View/ContentView.swift
  2. Add import sharedKit at the top of the file.
  3. Modify the Text view to include the Platform_iosKt.platform() information in the displayed string with \(Platform_iosKt.platform()).

Here is the final result of the file:

import SwiftUI
import sharedKit 

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            
            Text("Hello, \(Platform_iosKt.platform())!")
        }
        .padding()
    }
}
  1. Run the app either by clicking ⌘+R, or navigate to the Product menu and click Run.

This update checks whether the iOS app can call the platform() function from the shared module, which should return "iOS" when running on the iOS platform.

8024b5cc4bcd3f84.png

6. Add Swift/Kotlin Interface Enhancer (SKIE)

By default the native interface Kotlin produces is an Objective-C header. Swift is directly compatible with Objective-C, but Objective-C doesn't include all the modern features of Swift or Kotlin.

This is also the reason why in the previous example, you couldn't use the platform() call directly in the Swift code – KMP can't generate a global function, because Objective-C doesn't support global functions, only static functions encapsulated in a class, which is why you need to add Platform_iosKt.

To make the interface more Swift-friendly, you can use the Swift/Kotlin Interface Enhancer (SKIE) tool to improve the Swift interface of the :shared module.

The common features of SKIE are:

  • Better support for default arguments
  • Better support for sealed hierarchies (sealed class, sealed interface)
  • Better support for enum class with exhaustive handling in switch statements
  • Interoperability between Flow to AsyncSequence
  • Interoperability between suspend fun and async func
  1. Add co.touchlab.skie Gradle plugin to the libs.versions.toml file:
[versions]
skie = "0.10.1"

[plugins]
skie = { id = "co.touchlab.skie", version.ref = "skie" }
  1. Add the plugin to the root build.gradle.kts file
plugins {
   ...
   alias(libs.plugins.skie) apply false
}
  1. Add the plugin to the :shared module build.gradle.kts file:
plugins {
   ...
   alias(libs.plugins.skie)
}
  1. Gradle sync the project

Remove the static function call

Now when you rebuild the iOS app, you might not notice anything immediately, but you can remove the Platform_iosKt prefix and let the platform() function act as a global function.

Text("Hello, KMP! \(platform())")

This works, because SKIE (among other features) leverages the Swift API Notes, which adds information about APIs to better consume it from Swift code.

7. Congratulations

Congratulations, you've added the first shared Kotlin Multiplatform code to Android and iOS projects. While this is only a minimal starting point, you're now able to start discovering more advanced features and use cases for sharing code with KMP.

What's next?

Learn to use Jetpack Room to share a data layer between Android and iOS in the next codelab.

Learn more