Skip to main content
Kotlin Multiplatform Projects

Kotlin Multiplatform Projects: Expert Insights for Building Cross-Platform Apps Efficiently

Building cross-platform apps with Kotlin Multiplatform (KMP) promises code sharing across Android, iOS, web, and desktop, but teams often struggle with architecture, tooling, and real-world trade-offs. This guide provides expert insights on setting up KMP projects efficiently, covering core concepts like expect/actual declarations and shared module design, step-by-step workflows for integrating platform-specific APIs, and comparisons of popular frameworks such as Compose Multiplatform, Ktor, and SQLDelight. We explore common pitfalls—like dependency management conflicts and UI integration challenges—and offer actionable mitigation strategies. A decision checklist helps you evaluate whether KMP fits your team's skills and project requirements. With practical advice on testing, build configuration, and continuous integration, this article aims to help developers avoid costly mistakes and deliver maintainable cross-platform solutions. Last reviewed: May 2026.

Cross-platform development has long promised write-once-run-anywhere efficiency, but many teams have found that promise hollow when faced with platform-specific quirks and maintenance burdens. Kotlin Multiplatform (KMP) offers a different approach: share business logic while keeping native UIs, or go all-in with Compose Multiplatform for shared UI. This guide distills practical insights from teams that have navigated KMP adoption, focusing on what works, what doesn't, and how to decide if KMP is right for your next project. We cover core concepts, step-by-step workflows, tooling comparisons, and common pitfalls—all grounded in real-world scenarios, not theoretical ideals.

Why Kotlin Multiplatform? Understanding the Stakes and Reader Context

Many organizations face a painful choice: maintain separate codebases for Android and iOS (and sometimes web and desktop), or adopt a cross-platform framework that may limit native capabilities or introduce abstraction overhead. KMP aims to reduce duplication by allowing you to write shared code in Kotlin, which compiles to platform-specific binaries. The key value proposition is not necessarily 100% code sharing—rather, it's about sharing the parts that benefit most from reuse: data models, business rules, networking, and persistence.

The Real Cost of Duplication

In a typical project, a team might spend 30-40% of development time on business logic that is identical across platforms. When bugs appear, they must be fixed in two (or more) places, doubling effort. KMP addresses this by centralizing that logic in a shared module. However, the trade-off is that you must learn Kotlin Multiplatform's expect/actual mechanism, which allows you to declare platform-agnostic APIs and provide platform-specific implementations. Teams often underestimate this learning curve, especially when integrating with platform SDKs like Android's Jetpack or iOS's UIKit.

When KMP Makes Sense

KMP shines in projects where the business logic is complex and stable, and where platforms need to feel native. For example, a fintech app with intricate transaction rules and compliance checks benefits from shared logic, while its UI remains tailored to each platform's design guidelines. Conversely, a simple content-consumption app with minimal shared logic may not justify the overhead. Teams should also consider their existing Kotlin expertise—if your Android team already uses Kotlin, the learning curve is significantly lower than if you're coming from a pure Swift or JavaScript background.

Another critical factor is the maturity of the ecosystem. As of May 2026, KMP is stable for production use in Android and iOS, with growing support for web (via Kotlin/JS) and desktop (via Kotlin/Native). However, libraries for specific tasks—like camera access or Bluetooth—may still require platform-specific code. The decision to adopt KMP should be based on a honest assessment of your team's skills, the app's requirements, and the willingness to invest in tooling and build configuration.

Core Concepts: How Kotlin Multiplatform Works

At its heart, KMP uses a source set hierarchy. You define shared code in a commonMain source set, which is compiled for all targets. Platform-specific code lives in androidMain, iosMain, etc. The expect/actual mechanism lets you declare a function or class in common code (using the expect keyword) and provide platform-specific implementations (using actual). This is how you interact with platform APIs while keeping the interface shared.

Understanding the Expect/Actual Mechanism

Suppose you need to get the current device timezone. In commonMain, you write: expect fun getDeviceTimezone(): TimeZone. Then in androidMain, you implement it using Android's java.util.TimeZone, and in iosMain, you use NSTimeZone. The compiler ensures that each platform provides the actual implementation. This mechanism works well for simple APIs, but can become cumbersome for complex interfaces with many methods. In such cases, consider using a dependency injection framework or a platform-agnostic library like Ktor for networking, which already provides multiplatform implementations.

Shared Module Architecture

A typical KMP project structure includes a shared module containing common code, and separate app modules for each platform. The shared module can be a library that exports its API to the app modules. Inside the shared module, you organize code into packages like data (repositories, data sources), domain (use cases, models), and platform (expect declarations). This separation mirrors clean architecture and makes testing easier. For instance, you can write unit tests for domain logic in common code without any platform dependencies.

One common mistake is trying to push too much code into the shared module. UI code, for example, is often better kept platform-specific unless you use Compose Multiplatform. Similarly, direct access to platform sensors or file systems should be abstracted via expect/actual. The rule of thumb: share the logic that would be identical if written twice, and keep platform-specific code that would differ anyway.

Execution: Step-by-Step Workflow for Setting Up a KMP Project

Setting up a KMP project from scratch involves several steps, from choosing the right build system to configuring dependencies. Below is a practical workflow that many teams follow.

Step 1: Choose Your Targets and Build System

KMP officially supports Gradle as its build system. Start by creating a new project using the Kotlin Multiplatform wizard (available at kotlinlang.org) or manually setting up a Gradle multi-module project. Define your targets in the shared module's build.gradle.kts. For example, to target Android and iOS, you add androidTarget() and iosX64(), iosArm64(), iosSimulatorArm64(). For iOS, you also need to configure the framework output (e.g., binaries.framework { baseName = "shared" }).

Step 2: Configure Dependencies

Use the commonMain dependencies block to add multiplatform libraries. For example, Ktor for HTTP, SQLDelight for local storage, and kotlinx.serialization for JSON parsing. Platform-specific dependencies (like Android's OkHttp or iOS's Foundation) go in their respective source sets. Be cautious with version conflicts—use a Bill of Materials (BOM) if available. Many teams find it useful to create a buildSrc or version catalog to centralize dependency versions.

Step 3: Implement Shared Logic

Start with the domain layer: define your data models (using @Serializable data classes), repository interfaces, and use cases. Then implement the data layer: create platform-agnostic implementations using Ktor for API calls and SQLDelight for local caching. For platform-specific behavior (like file storage), use expect/actual. For example, expect a PlatformContext class that provides the app's file directory, and implement it on Android using Context.filesDir and on iOS using NSFileManager.

Step 4: Integrate with Platform Apps

In your Android app module, add a dependency on the shared module. You can then call shared functions directly from your Activities or ViewModels. For iOS, you'll need to create an Xcode project that imports the shared framework. Use a script phase in Xcode to build the Kotlin framework automatically. Many teams use the embedAndSignAppleFrameworkForXcode Gradle task to streamline this. Once integrated, you call shared functions from Swift as if they were native classes.

Step 5: Test and Iterate

Write unit tests for common code using kotlin.test and run them on JVM (fast) or native targets (for platform-specific behavior). For integration tests, you may need to run on actual devices or simulators. Use continuous integration (CI) pipelines that build both Android and iOS targets. Common CI tools like GitHub Actions or Jenkins can be configured with Gradle tasks to build the shared module and run tests.

Tools, Stack, and Economics: Choosing the Right Libraries and Managing Costs

Selecting the right libraries is crucial for KMP project success. The ecosystem has matured, but not every library is multiplatform-ready. Below is a comparison of commonly used libraries across key categories.

CategoryLibraryMultiplatform SupportProsCons
NetworkingKtorFull (Android, iOS, JVM, JS, Native)Lightweight, coroutine-based, easy to testSmaller community than Retrofit; less plugin ecosystem
NetworkingOkHttp (via Ktor engine)Android only (directly)Mature, widely usedNot multiplatform; must use Ktor engine for iOS
Local StorageSQLDelightFull (Android, iOS, JVM, JS, Native)Type-safe SQL, compile-time verification, multiplatformLearning curve for SQL generation; not NoSQL
Local StorageRoomAndroid onlyFamiliar to Android devs, integrates with JetpackNot multiplatform; requires platform-specific code
Serializationkotlinx.serializationFullFirst-party, compile-time, supports JSON, ProtoBuf, CBORRequires compiler plugin; less flexible than Gson for complex cases
UICompose MultiplatformAndroid, iOS, Desktop, Web (experimental)Shared UI, declarative, moderniOS support still maturing; performance overhead on iOS
UINative UI (SwiftUI + Jetpack Compose)Platform-specificBest performance, full platform featuresNo code sharing for UI; more code to maintain

Economic Considerations

Adopting KMP can reduce development costs by eliminating duplicate business logic, but it introduces new costs: training, build configuration, and potential debugging complexity. Teams should budget for a pilot project—perhaps a small feature or a new app—to evaluate the learning curve and tooling maturity. Many practitioners report that the break-even point occurs after the first two platform releases, as shared code stabilizes and platform-specific code becomes thinner. However, if your app requires heavy use of platform-specific features (AR, Bluetooth, camera), the shared percentage may be low, making KMP less economical.

Growth Mechanics: Scaling KMP Projects and Maintaining Momentum

Once a KMP project is up and running, the focus shifts to scaling the codebase and team. Growth in this context means adding more features, more platforms, and more developers—all while keeping the shared codebase healthy.

Adding New Platforms

KMP's architecture makes adding a new platform (e.g., desktop or web) relatively straightforward. You create a new source set for that platform and provide actual implementations for any expect declarations. However, you may need to add new expect declarations if the new platform lacks certain APIs. For example, adding desktop support may require expect declarations for file dialogs or system tray. Plan for this by keeping expect declarations minimal and generic. Also, consider using Compose Multiplatform for UI if you want to share UI across desktop and mobile, but be aware that desktop UI patterns differ from mobile.

Onboarding New Developers

New team members often struggle with the expect/actual pattern and the Gradle build configuration. Create a developer onboarding guide that includes a sample project, a list of common pitfalls, and a glossary of KMP terms. Pair programming during the first week can help. Also, enforce code reviews that specifically check for proper use of platform-specific code—developers sometimes accidentally put platform-specific code in commonMain, which will not compile for other targets.

Maintaining Build Performance

As the shared module grows, build times can increase, especially for iOS targets because Kotlin/Native compilation is slower than JVM. Mitigate this by using Gradle build caching, incremental compilation, and by splitting the shared module into smaller submodules if necessary. For example, separate networking logic from business logic into different modules so that changes to one don't trigger recompilation of the other. Some teams also use remote build caches to share compiled artifacts across developers.

Continuous Integration and Delivery

Set up CI pipelines that build and test all targets. For iOS, you'll need a macOS runner. Use Gradle tasks like linkDebugFrameworkIosArm64 to produce the framework, and then run Xcode tests. Automate versioning and publishing of the shared framework to a private repository (e.g., via GitHub Packages or Artifactory) so that iOS developers can consume it without rebuilding from source. This decouples the shared module's release cycle from the app's release cycle.

Risks, Pitfalls, and Mitigations

No technology is without risks. KMP has several known pitfalls that can derail a project if not addressed early.

Dependency Management Conflicts

Because KMP compiles to multiple platforms, dependencies must be available for each target. A library that works on Android may not have an iOS artifact. Always check the library's documentation for multiplatform support. Use the expect keyword to abstract away dependencies that are not multiplatform. For example, if you need a DI framework, Koin has multiplatform support, while Dagger does not. Mitigation: create a dependency compatibility matrix before starting the project, and prefer first-party Kotlin multiplatform libraries.

UI Integration Challenges

If you choose to keep native UIs, you must bridge data between the shared module and the UI layer. On Android, this is straightforward via ViewModels that observe shared flows. On iOS, you need to ensure that the shared framework's objects are accessible from Swift. Use @ObjCName annotations to rename Kotlin functions for Swift, and consider using a wrapper layer that converts Kotlin types to Swift types. A common pitfall is that Kotlin's coroutine Flow does not map directly to Swift's Combine framework; you may need to use a bridge library or convert flows to callback-based APIs.

Build Configuration Complexity

Setting up the Gradle build for multiple targets can be error-prone. Common issues include incorrect target names, missing platform-specific dependencies, and misconfigured framework linking. Mitigation: use the Kotlin Multiplatform wizard to generate the initial project, and keep the build files as simple as possible. Avoid custom Gradle plugins unless necessary. Document the build setup in a README so that new team members can reproduce it.

Testing Platform-Specific Code

Testing actual implementations on iOS requires running tests on a Mac with an iOS simulator. This adds CI complexity and cost. One mitigation is to keep actual implementations as thin as possible—ideally, they just delegate to a platform API. Then you can test the shared logic thoroughly in common tests, and rely on manual or integration tests for the thin platform layer. Also, consider using mocking frameworks like MockK that support multiplatform.

Performance Overhead on iOS

Kotlin/Native generates native binaries, but there can be overhead from memory management (Kotlin uses its own allocator) and from interop with Objective-C. For most apps, this overhead is negligible, but for performance-critical code (e.g., real-time audio processing), you may need to write platform-specific implementations. Profile early and often. If you find a bottleneck, consider moving that code to a platform-specific actual implementation.

Decision Checklist and Mini-FAQ

Before committing to KMP, run through this checklist to assess fit:

  • Team skills: Does your team already know Kotlin? If not, factor in training time (at least 2-4 weeks for experienced developers).
  • Shared logic percentage: Estimate how much code will be shared. If less than 30%, KMP may not be worth the overhead.
  • Platform-specific features: Does your app rely heavily on platform SDKs (camera, Bluetooth, sensors)? If yes, plan for more actual implementations.
  • UI approach: Are you willing to use Compose Multiplatform or maintain separate UIs? Both have trade-offs.
  • Build infrastructure: Do you have macOS CI runners for iOS builds? If not, factor in cost.
  • Library availability: Are the libraries you need available for all target platforms? Check before starting.
  • Long-term maintenance: Is your organization committed to maintaining KMP-specific build configurations and expect/actual declarations?

Frequently Asked Questions

Q: Can I use KMP with an existing Android app? Yes, you can add a shared module incrementally. Start by moving data models and networking code to the shared module, then gradually shift business logic. Your existing Android app can depend on the shared module without affecting the iOS app until you're ready.

Q: How does KMP compare to Flutter or React Native? KMP is more flexible in terms of UI—you can choose to share UI or not—while Flutter and React Native enforce their own UI frameworks. KMP also has better performance for computation-heavy tasks because it compiles to native code. However, Flutter has a larger ecosystem for UI components and hot reload. The choice depends on your team's skills and UI requirements.

Q: Is KMP production-ready for iOS? Yes, as of May 2026, KMP is stable for iOS. Major companies like Netflix and McDonald's have used KMP in production. However, you may encounter edge cases with interop or performance; the community is active and responsive.

Q: What are the best resources to learn KMP? The official Kotlin documentation is excellent. Additionally, the Kotlin Slack community and the #multiplatform channel are very helpful. There are also several books and online courses, but always verify that they cover the latest version (1.9.x or later).

Synthesis and Next Actions

Kotlin Multiplatform is a powerful tool for sharing business logic across platforms, but it is not a silver bullet. Success requires careful planning, a willingness to invest in build configuration, and a clear understanding of when to share and when to keep code platform-specific. Start small: pick a single feature or a new app, build a shared module with a few expect/actual declarations, and measure the impact on development speed and code quality.

Next steps for teams considering KMP:

  1. Evaluate your current codebase: Identify the parts that are duplicated across platforms and estimate the effort to extract them into a shared module.
  2. Set up a prototype: Follow the step-by-step workflow above to create a minimal shared module that compiles for Android and iOS. Integrate it into a simple app (e.g., a weather app with a network call).
  3. Test the developer experience: Have one developer from each platform work on the prototype for a week. Note pain points and areas where the tooling falls short.
  4. Decide on UI strategy: Choose between Compose Multiplatform or native UIs based on your team's skills and design requirements. If in doubt, start with native UIs to minimize risk.
  5. Plan for CI: Set up a CI pipeline that builds and tests all targets. This is non-negotiable for a production project.
  6. Monitor and iterate: After launch, track metrics like crash rates, build times, and developer satisfaction. Adjust your architecture as needed.

Remember that KMP is a means to an end—delivering value to users faster and with fewer bugs. Keep that goal in mind, and you'll navigate the trade-offs effectively.

About the Author

This article was prepared by the editorial team for this publication. We focus on practical explanations and update articles when major practices change.

Last reviewed: May 2026

Share this article:

Comments (0)

No comments yet. Be the first to comment!