KMM: Kotlin Multiplatform Mobile Guide
Kotlin Multiplatform Mobile (KMM) lets you write a single codebase in Kotlin and share it between Android and iOS. Instead of maintaining two separate native stacks, you keep business logic, networking, and data layers in one place while still accessing each platform’s UI toolkit. This approach reduces duplication, speeds up iteration, and ensures feature parity across devices. In this guide we’ll walk through setting up KMM, building a shared module, and wiring it into real‑world Android and iOS apps.
What Is KMM?
KMM is an extension of Kotlin Multiplatform that focuses on mobile targets: android() and ios(). The compiler produces a JVM artifact for Android and a native framework for iOS, both consuming the same common Kotlin code. Under the hood, Kotlin’s expect/actual mechanism lets you declare platform‑agnostic APIs and provide concrete implementations per target.
Because the shared module compiles to native binaries for iOS, you get zero‑runtime overhead and full interoperability with Swift/Obj‑C. On Android you simply add the module as a regular Gradle dependency. This means you can use existing Android libraries in the shared layer, while still calling iOS‑specific APIs when needed.
Why Choose KMM?
- Code reuse: Up to 80 % of your code can be shared, cutting down on duplicated effort.
- Consistent business rules: Bugs fixed in the shared layer instantly propagate to both platforms.
- Faster onboarding: New developers only need to learn Kotlin and the KMM workflow.
- Future‑proof: Kotlin’s roadmap includes broader multiplatform support, making KMM a strategic investment.
Pro tip: Start by extracting pure‑logic components (e.g., validation, encryption) before tackling UI‑related code. This gives you quick wins and a solid foundation for later expansion.
Setting Up the Development Environment
The first step is to install the latest version of Android Studio (Arctic Fox or newer) with the Kotlin Multiplatform plugin. Open the IDE, go to Plugins → Marketplace**, search for “Kotlin Multiplatform Mobile”, and install it. Restart Android Studio to activate the plugin.
Next, ensure you have Xcode (14+ recommended) on your macOS machine. KMM relies on the Xcode toolchain to compile the iOS framework. Verify the command‑line tools are set correctly by running xcode-select --install in Terminal.
Configuring the Gradle Build
In your project’s settings.gradle.kts, enable the multiplatform plugin and define the targets:
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
rootProject.name = "MyKMMApp"
include(":shared")
In the shared/build.gradle.kts file, add the Kotlin Multiplatform and CocoaPods plugins, then declare the Android and iOS targets:
plugins {
kotlin("multiplatform")
id("com.android.library")
id("org.jetbrains.kotlin.native.cocoapods")
}
kotlin {
android()
ios {
binaries {
framework {
baseName = "SharedFramework"
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
implementation("io.ktor:ktor-client-core:2.3.0")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.0")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-ios:2.3.0")
}
}
}
}
Sync the project, and Android Studio will generate the necessary source sets (commonMain, androidMain, iosMain). You’re now ready to write shared Kotlin code.
Creating Your First KMM Project
Choose “File → New → New Project” and select the “KMM Application” template. The wizard scaffolds a sample app with a shared module, an Android app, and an iOS Xcode project that references the generated framework. Accept the defaults, then click “Finish”.
The generated sample demonstrates a simple Greeting class in the shared module, which you can call from both platforms. Let’s replace it with something more practical: a repository that fetches a list of posts from a public API.
Shared Repository Implementation
In shared/src/commonMain/kotlin/com/example/repository/PostsRepository.kt, define an interface and its implementation using Ktor:
package com.example.repository
import io.ktor.client.*
import io.ktor.client.request.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
data class Post(val id: Int, val title: String, val body: String)
interface PostsRepository {
fun getPosts(): Flow<List<Post>>
}
class PostsRepositoryImpl(private val client: HttpClient) : PostsRepository {
override fun getPosts(): Flow<List<Post>> = flow {
val posts: List<Post> = client.get("https://jsonplaceholder.typicode.com/posts")
emit(posts)
}
}
Notice the use of Flow for asynchronous streaming, which works seamlessly on both Android (via coroutines) and iOS (via Kotlin/Native). The same repository can be injected into view models on each platform without any changes.
Sharing Business Logic Across Platforms
Beyond networking, you’ll often need utilities like JSON parsing, encryption, or caching. Kotlin’s standard library, along with multiplatform-friendly libraries (e.g., kotlinx-serialization, SQLDelight), can be used in the commonMain source set. Here’s a quick example of a serializable data class:
import kotlinx.serialization.Serializable
@Serializable
data class User(val id: String, val name: String, val email: String)
Because kotlinx-serialization generates platform‑specific serializers, you can parse JSON on Android and iOS with identical code. This eliminates the need for separate Swift or Java parsers, keeping your data contracts single‑sourced.
Expect/Actual for Platform‑Specific Features
When you need to access a feature that isn’t available in the common library—like retrieving the device’s locale—you declare an expect function in commonMain and provide actual implementations in each platform source set.
// commonMain
expect fun getCurrentLocale(): String
// androidMain
actual fun getCurrentLocale(): String = java.util.Locale.getDefault().toString()
// iosMain
actual fun getCurrentLocale(): String = NSLocale.currentLocale.localeIdentifier
This pattern keeps your shared code clean while still allowing deep integration with native APIs.
Integrating with Android and iOS UI
After the shared layer is ready, you’ll need to expose its functionality to the UI layers. On Android, the shared module appears as a regular Gradle dependency, so you can use Dagger/Hilt or Koin for dependency injection. In iOS, the generated framework is imported into Xcode, and you can call Kotlin classes directly from Swift.
Android Integration
In androidApp/src/main/java/com/example/app/MainViewModel.kt, inject the repository and expose a LiveData stream:
class MainViewModel(
private val repository: PostsRepository
) : ViewModel() {
private val _posts = MutableLiveData<List<Post>>()
val posts: LiveData<List<Post>> = _posts
init {
viewModelScope.launch {
repository.getPosts().collect {
_posts.postValue(it)
}
}
}
}
The UI (e.g., a RecyclerView) observes posts and updates automatically. All networking, error handling, and data transformation remain in the shared code.
iOS Integration
Open the Xcode workspace generated by the KMM plugin. Import the framework in a Swift file:
import SharedFramework
class PostsViewController: UIViewController {
private let viewModel = PostsViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.posts.observe { [weak self] posts in
// Update UI on main thread
DispatchQueue.main.async {
self?.render(posts)
}
}
viewModel.fetchPosts()
}
private func render(_ posts: [Post]) {
// Populate a UITableView or SwiftUI List
}
}
The PostsViewModel lives in the shared module and uses Kotlin coroutines under the hood. Thanks to Kotlin/Native’s automatic bridging, Swift sees Kotlin classes as regular Swift types.
Real‑World Use Cases
Many companies have adopted KMM for production apps. Here are three common scenarios where KMM shines:
- FinTech apps: Share encryption, transaction validation, and API clients while keeping native UI for regulatory compliance.
- Health & fitness trackers: Reuse sensor data processing, analytics, and cloud sync logic across Android wearables and iOS HealthKit integrations.
- E‑commerce platforms: Maintain a single checkout flow, cart management, and recommendation engine, reducing time‑to‑market for new features.
In each case, the shared module handles the heavy lifting—network calls, business rules, and data persistence—while the UI teams focus on platform‑specific experiences.
Pro Tips for Scaling KMM Projects
Modularize early. Split the shared code into logical Gradle modules (e.g.,network,domain,storage). This isolates responsibilities, speeds up incremental builds, and makes it easier to test each layer independently.
Leverage Kotlin’s sealed classes. Use them for representing UI states (Loading,Success,Error) and share them across platforms. Swift can pattern‑match on these sealed classes just like native enums.
Testing Strategies
One of KMM’s biggest advantages is the ability to write unit tests once and run them on the JVM, Android, and iOS simulators. Place your tests in commonTest to cover shared logic, and use platform‑specific test sources for UI or framework interactions.
Unit Testing Shared Code
Here’s a simple test for the PostsRepositoryImpl using MockK and the Ktor mock engine:
class PostsRepositoryTest {
private val mockEngine = MockEngine { request ->
respond(
content = """[{"id":1,"title":"Test","body":"Body"}]""",
headers = headersOf("Content-Type", "application/json")
)
}
private val client = HttpClient(mockEngine) {
install(JsonFeature) {
serializer = KotlinxSerializer()
}
}
private val repository = PostsRepositoryImpl(client)
@Test
fun `fetch posts returns non‑empty list`() = runBlocking {
repository.getPosts().first().let { posts ->
assertTrue(posts.isNotEmpty())
assertEquals(1, posts.first().id)
}
}
}
Run this test on the JVM; the same code works on iOS because the mock engine is multiplatform.
UI Testing on Each Platform
For Android, use Espresso or Jetpack Compose testing libraries. For iOS, write XCTest cases that invoke the Kotlin framework’s public API. Keep UI tests thin—focus on interaction, while business logic stays in shared unit tests.
Conclusion
Kotlin Multiplatform Mobile empowers you to write once, run everywhere, without sacrificing native performance or user experience. By structuring your project with a clean shared module, leveraging expect/actual for platform nuances, and integrating with existing Android and iOS toolchains, you can dramatically cut development time and technical debt. Start small—extract a single repository or validation class—then iterate toward a full‑blown shared codebase. With the right tooling and disciplined modularization, KMM becomes a sustainable strategy for modern mobile development.