Kotlin Multiplatform Mobile (KMM) is making waves in cross-platform development. It allows you to write shared code for both Android and iOS apps, eliminating the need to duplicate functionality across two separate codebases. While you can still include platform-specific code where necessary, KMM lets you handle most of the app logic in one place. This approach saves time, reduces effort, and offers flexibility—making it a boon for modern app development.
In this blog, we’ll take a deep dive into KMM: what it is, how it works, and why you should consider using it for your mobile projects. Plus, we’ll walk you through a practical example.
What is Kotlin Multiplatform Mobile (KMM)?
Let’s start with a scenario: You’re building an app for both Android and iOS, and you’re tired of writing the same logic twice. Once for Android in Kotlin, and once again for iOS in Swift. Wouldn’t it be nice to just write that logic once and use it for both platforms? That’s exactly where Kotlin Multiplatform Mobile (KMM) shines.
KMM lets you write shared business logic in Kotlin and use it across both Android and iOS. But you still maintain the freedom to write platform-specific code when needed. The beauty of KMM lies in its balance: you can share as much code as you want, while still having access to platform-specific APIs, libraries, and native UI. It’s not about “write once, run anywhere”; it’s more like “write once, adapt where necessary.”
It’s like having the best of both worlds – your shared codebase acts as the glue that holds your logic together, while the platform-specific parts allow you to give users the full native experience.
Why Choose KMM?
You might be thinking, “There are already several cross-platform tools, like Flutter and React Native, so why KMM?” Let’s unpack some reasons why KMM might be a great fit for you:
Native UI with Shared Logic
KMM focuses on sharing business logic while letting you write native UI code. This means you can create a truly platform-specific experience for users, but avoid the repetitive task of writing the same logic twice. For example, the user interface can be implemented using Jetpack Compose on Android and SwiftUI on iOS, while the data handling, API calls, and domain logic can be shared.
Stay in the Kotlin Ecosystem
Kotlin is well-loved by Android developers for its concise syntax and modern features. With KMM, you can use your existing Kotlin knowledge for iOS as well. No need to jump between different programming languages or tools. It’s like finding out your favorite pair of jeans fits perfectly on both Android and iOS.
Flexible Adoption
Unlike some other cross-platform frameworks, KMM doesn’t require you to completely rewrite your app from scratch. You can adopt it gradually. Start with a small piece of shared code in an existing project and gradually extend it as needed. This makes KMM ideal for teams that already have Android and iOS apps in production and want to start reaping the benefits of shared code without a complete overhaul.
Reduced Development Time
By writing the core business logic once and using it across both platforms, you save a significant amount of time. Your team can focus on the more creative aspects, like designing beautiful user interfaces, while the shared code handles the heavy lifting of your app’s logic.
How KMM Works: A Look at its Architecture
KMM is built on the idea of separating the shared code and the platform-specific code. The shared code contains all the parts of your app that don’t depend on any particular platform, while the platform-specific modules handle UI and any platform-specific APIs.
The high-level structure of a KMM project looks like this:
- Shared Module: This is where all the shared code lives. It contains the business logic, data handling, networking code, and any models that can be shared across Android and iOS.
- Platform-Specific Code: This is where the Android and iOS specific components reside. The platform-specific modules handle things like user interface, accessing native APIs, and anything else that requires a unique approach on each platform.
Here’s a simplified diagram to visualize the architecture:
+--------------------------+
| Shared Module |
| (Shared Kotlin Code) |
+--------------------------+
/ \
/ \
+---------+ +-----------+
| Android | | iOS |
| Module | | Module |
| (UI + | | (UI + |
| Platform| | Platform |
| code) | | code) |
+---------+ +-----------+
In this architecture, the shared module handles most of your app’s logic, while platform-specific code is used to implement UI and any native features unique to Android or iOS.
Setting Up a KMM Project
Ready to jump in? Setting up KMM is much easier than it sounds. Here’s how to get started.
Step 1: Initializing a Project
First, ensure you’re using the latest version of Android Studio with the Kotlin Multiplatform Mobile plugin installed. You can find the plugin in the marketplace within Android Studio. Once everything is set up, create a new project and select the Kotlin Multiplatform Mobile App template.
Android Studio will guide you through the process. Don’t worry – it’s much simpler than setting up that IKEA bookshelf you’ve been avoiding.
Step 2: The Structure of a KMM Project
After setting up the project, you’ll see three main directories:
- androidApp: Contains the Android-specific code for your app, such as UI layouts, platform-specific libraries, and Android resources.
- iosApp: This is where you write your iOS-specific code in Swift. Here, you’ll define the UI and use iOS-native libraries.
- shared: The shared module that contains your business logic, models, and network operations. This is where most of the heavy lifting happens.
The project structure looks something like this:
- myKMMProject/
- androidApp/
- iosApp/
- shared/
- src/
- commonMain/
- androidMain/
- iosMain/
Step 3: Writing Shared Logic
Let’s add some shared business logic to our app. For instance, we want to write code that greets the user depending on the platform they’re using. In the shared
module, write the following Kotlin code:
// Shared code
class Greeting {
fun greet(): String {
return "Hello from ${Platform().platform}!"
}
}
expect class Platform() {
val platform: String
}
Here, expect
is a keyword in Kotlin that tells the compiler: “I expect this class to be implemented separately for each platform.” The platform-specific code will provide the actual implementation of the Platform
class.
Step 4: Platform-Specific Implementations
Now, let’s write the platform-specific code that provides the platform
string for Android and iOS.
Android Implementation:
// androidMain
actual class Platform {
actual val platform: String = "Android"
}
iOS Implementation:
// iosMain
actual class Platform {
actual val platform: String = "iOS"
}
Now, when you call greet()
, the app will return “Hello from Android!” or “Hello from iOS!” based on the platform. How cool is that?
Step 5: Running Code on Both Platforms
Once the shared code and platform-specific code are written, it’s time to run the app. For Android, you can run it directly in the Android Studio emulator or on an Android device. For iOS, you’ll need to open the iosApp
in Xcode and run it on an iPhone simulator or physical device.
And there you go! You’ve just created a KMM project that works on both Android and iOS, using shared logic and platform-specific UI.
Building a Simple Counter App with KMM
Now that we’ve got the basics covered, let’s build a simple counter app to see KMM in action.
Shared Code for Logic
In the shared module, we’ll write a Counter
class that handles the business logic for our app.
// shared/src/commonMain/kotlin/Counter.kt
class Counter {
private var count = 0
fun increment() {
count++
}
fun getCount(): Int {
return count
}
}
This class is simple – it increments a counter and returns the current count. It’s the core of our app, and it will be shared across both platforms.
Platform-Specific UI Code
For Android, we’ll use Jetpack Compose to build the user interface. Compose is a modern UI toolkit for building native UIs in Android.
// androidApp/src/main/java/com/softaai/CounterActivity.kt
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.*
import androidx.compose.runtime.*
import com.example.shared.Counter
class CounterActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val counter = Counter()
setContent {
var count by remember { mutableStateOf(counter.getCount()) }
Column {
Text("Count: $count")
Button(onClick = {
counter.increment()
count = counter.getCount()
}) {
Text("Increment")
}
}
}
}
}
For iOS, we’ll use SwiftUI to build the interface. SwiftUI is Apple’s modern declarative UI framework.
// iosApp/CounterView.swift
import SwiftUI
import shared
struct CounterView: View {
let counter = Counter()
@State private var count: Int32 = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
counter.increment()
count = counter.getCount()
}
}
}
}
Both platforms now share the same counter logic, while keeping their UIs native and tailored to each platform’s design principles.
Where KMM Shines in Real-World Projects
KMM is already being used by major companies such as Netflix, VMware, and Philips. It’s perfect for applications where business logic needs to be shared across platforms but native user interfaces are still desired.
Take a banking app as an example. The security algorithms, business rules, and data models can be shared across Android and iOS using KMM, while each platform has its own native UI tailored for the best user experience.
This reduces duplication in writing and testing the same logic while allowing developers to focus on delivering excellent platform-specific UIs.
Let’s see one more use case for KMM.
KMM Use Case: Building a News Application
In this section, we’ll walk through a practical use case for Kotlin Multiplatform Mobile (KMM) by creating a simple News Application. This app will fetch and display the latest news articles from a remote API, with shared business logic across Android and iOS platforms, but with platform-specific UI components to maintain the native look and feel.
The key components of the app will include:
- Fetching News Data: The app will retrieve news articles from an API. This will be done using shared code, so both Android and iOS will use the same logic for data retrieval.
- Displaying Articles: The user interface will be platform-specific. We’ll use Jetpack Compose for Android and SwiftUI for iOS.
- Modeling the Data: We’ll define common data models to represent the articles, shared across both platforms.
Setting Up the KMM Project
If you’re new to KMM, setting up a project might seem daunting, but it’s pretty straightforward.
Step 1: Create the KMM Project
First, open Android Studio and create a new Kotlin Multiplatform Mobile (KMM) project.
- Choose a KMM Project Template: Select a KMM template to generate the shared and platform-specific code.
- Configure Android & iOS Modules: Android Studio will automatically create separate modules for Android (
androidApp
) and iOS (iosApp
), as well as the shared module where you’ll write most of the logic.
Once your project structure is in place, you’ll have something like this:
- MyNewsApp/
- androidApp/ # Android-specific UI code
- iosApp/ # iOS-specific UI code
- shared/ # Shared business logic
- src/
- commonMain/ # Shared logic between Android and iOS
- androidMain/ # Android-specific logic
- iosMain/ # iOS-specific logic
Fetching News Articles (Shared Logic)
The first thing we need is a way to retrieve news articles from a public API. For this use case, let’s assume we’re using the News API, which provides a free service to fetch the latest articles.
Step 1: Create a News Data Model
In the shared module, create a data class that will represent a news article:
// shared/src/commonMain/kotlin/com/mynewsapp/models/NewsArticle.kt
package com.mynewsapp.models
data class NewsArticle(
val title: String,
val description: String,
val url: String,
val urlToImage: String?
)
Step 2: Implement the News API Client
Next, we’ll implement the logic for fetching news from the API. We’ll use Ktor (a Kotlin networking library) to make the network request, which works on both Android and iOS.
First, add Ktor as a dependency in the shared module:
// shared/build.gradle.kts
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:2.x.x")
implementation("io.ktor:ktor-client-json:2.x.x")
implementation("io.ktor:ktor-client-serialization:2.x.x")
}
}
}
}
Then, write the API client to fetch the latest news:
// shared/src/commonMain/kotlin/com/mynewsapp/network/NewsApiClient.kt
package com.mynewsapp.network
import com.mynewsapp.models.NewsArticle
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
class NewsApiClient {
private val client = HttpClient {
install(JsonFeature) {
serializer = KotlinxSerializer(Json { ignoreUnknownKeys = true })
}
install(Logging) {
level = LogLevel.INFO
}
}
suspend fun getTopHeadlines(): List<NewsArticle> {
val response: NewsApiResponse = client.get("https://newsapi.org/v2/top-headlines?country=us&apiKey=YOUR_API_KEY")
return response.articles.map {
NewsArticle(
title = it.title,
description = it.description,
url = it.url,
urlToImage = it.urlToImage
)
}
}
}
@Serializable
data class NewsApiResponse(val articles: List<ArticleDto>)
@Serializable
data class ArticleDto(
val title: String,
val description: String,
val url: String,
val urlToImage: String?
)
The NewsApiClient
class handles the network request to fetch top news headlines. It parses the JSON response into Kotlin data models, which are then used throughout the app.
Step 3: Handle Business Logic in a Repository
To keep things organized, let’s create a NewsRepository
that will manage data retrieval and handle any business logic related to articles.
// shared/src/commonMain/kotlin/com/mynewsapp/repository/NewsRepository.kt
package com.mynewsapp.repository
import com.mynewsapp.models.NewsArticle
import com.mynewsapp.network.NewsApiClient
class NewsRepository(private val apiClient: NewsApiClient) {
suspend fun getTopHeadlines(): List<NewsArticle> {
return apiClient.getTopHeadlines()
}
}
Now, both the Android and iOS apps will use this repository to get the latest news.
Note – To keep it simple, I haven’t gone into much detail about different architectures like MVVM in Android and VIPER in iOS. While these architectures usually include an additional layer like the ViewModel in Android MVVM or the Presenter in VIPER, I have directly used the repository here to simplify the implementation, though it’s typically recommended to use those layers for better separation of concerns.
Displaying News on Android (Jetpack Compose)
Now that we’ve handled the shared logic for fetching news articles, let’s build the Android user interface using Jetpack Compose.
Step 1: Set Up Compose in the Android Module
Ensure you’ve added the necessary Compose dependencies in the androidApp
module:
// androidApp/build.gradle.kts
dependencies {
implementation("androidx.compose.ui:ui:1.x.x")
implementation("androidx.compose.material:material:1.x.x")
implementation("androidx.compose.ui:ui-tooling:1.x.x")
implementation("androidx.activity:activity-compose:1.x.x")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.x.x")
}
Step 2: Create the UI in Compose
In the Android module, create a simple UI that fetches and displays the news articles using Jetpack Compose.
// androidApp/src/main/java/com/mynewsapp/NewsActivity.kt
package com.mynewsapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.rememberImagePainter
import com.mynewsapp.repository.NewsRepository
import kotlinx.coroutines.launch
class NewsActivity : ComponentActivity() {
private val newsRepository = NewsRepository(NewsApiClient())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NewsApp()
}
}
@Composable
fun NewsApp() {
val newsArticles = remember { mutableStateOf<List<NewsArticle>?>(null) }
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(Unit) {
coroutineScope.launch {
val articles = newsRepository.getTopHeadlines()
newsArticles.value = articles
}
}
Scaffold(
topBar = { TopAppBar(title = { Text("Latest News") }) }
) {
NewsList(newsArticles.value)
}
}
@Composable
fun NewsList(newsArticles: List<NewsArticle>?) {
LazyColumn {
newsArticles?.let { articles ->
items(articles.size) { index ->
NewsItem(articles[index])
}
}
}
}
@Composable
fun NewsItem(article: NewsArticle) {
Column(modifier = Modifier.padding(16.dp)) {
Text(article.title, style = MaterialTheme.typography.h6)
Spacer(modifier = Modifier.height(8.dp))
article.urlToImage?.let {
Image(
painter = rememberImagePainter(data = it),
contentDescription = null,
modifier = Modifier.height(180.dp).fillMaxWidth(),
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(article.description ?: "", style = MaterialTheme.typography.body2)
}
}
}
Here, we use Jetpack Compose to create a list of news articles. The NewsItem
composable displays each article’s title, description, and image, fetching the data from the NewsRepository
.
Displaying News on iOS (SwiftUI)
Let’s now build the UI for iOS using SwiftUI. The data retrieval logic remains the same, as we’ve already handled it in the shared module.
Step 1: Set Up the SwiftUI View
In the iOS module, create a SwiftUI view that will display the news articles.
// iosApp/NewsView.swift
import SwiftUI
import shared
struct NewsView: View {
@State private var newsArticles: [NewsArticle] = []
let repository = NewsRepository(apiClient: NewsApiClient())
var body: some View {
NavigationView {
List(newsArticles, id: \.title) { article in
NewsRow(article: article)
}
.navigationTitle("Latest News")
.onAppear {
repository.getTopHeadlines { articles, error in
if let articles = articles {
self.newsArticles = articles
}
}
}
}
}
}
struct NewsRow: View {
var article: NewsArticle
var body: some View {
VStack(alignment: .leading) {
Text(article.title).font(.headline)
if let urlToImage = article.urlToImage {
AsyncImage(url: URL(string: urlToImage)) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.frame(height: 200)
.cornerRadius(10)
}
Text(article.description ?? "").font(.subheadline)
}
.padding()
}
}
struct NewsView_Previews: PreviewProvider {
static var previews: some View {
NewsView()
}
}
In SwiftUI, we use a List
to display the news articles. Similar to Compose, the NewsRow
component shows the title, image, and description of each article.
Testing and Running the App
Once you’ve implemented the UI for both Android and iOS, you can now test and run your news app.
- For Android: Simply run the
androidApp
module using an Android emulator or a real Android device via Android Studio. - For iOS: Open the
iosApp
module in Xcode and run the app on an iPhone simulator or physical device.
You’ll have a fully functional news app running on both platforms with shared logic for fetching news and platform-specific UI for displaying it.
Challenges & Things to Keep in Mind
While KMM is a powerful tool, it does have its challenges:
- Learning Curve: For iOS developers unfamiliar with Kotlin, there might be a bit of a learning curve. However, the syntax is similar enough to Swift that the transition is usually smooth.
- Tooling Limitations: While Android Studio supports KMM, Xcode is still necessary for iOS builds and debugging, so you’ll need to use both tools. The KMM ecosystem is still growing, and some libraries may not yet support iOS.
- Library Support: Not all third-party libraries in Kotlin are compatible with iOS yet, but this is improving as KMM continues to evolve.
Looking Ahead: The Future of KMM
The Kotlin ecosystem is rapidly evolving, and KMM is at the forefront of cross-platform mobile development. With JetBrains and Google investing in its development, KMM is becoming more robust and easier to use. As more libraries become compatible, and tooling improves, KMM is poised to be a go-to solution for teams looking to build cross-platform apps without compromising on user experience.
Conclusion
Kotlin Multiplatform Mobile offers a modern, efficient approach to mobile app development by allowing you to share business logic between Android and iOS. You get the benefit of shared code without sacrificing the native experience that users expect.
While it’s not a one-size-fits-all solution, KMM is a flexible and powerful tool that can streamline development, reduce bugs, and make maintaining your codebase much easier.
So go ahead, give KMM a try, and start saving time and effort by writing shared logic that works seamlessly across platforms. With the time you save, maybe you can finally tackle that IKEA bookshelf!