Kotlin Multiplatform (KMP) promises a unified codebase for Android, iOS, web, and desktop—but the gap between promise and practice can be wide. Teams often find themselves tangled in build configuration, platform-specific APIs, and unexpected maintenance overhead. This guide is for modern professionals—tech leads, senior engineers, and architects—who want to cut through the hype and understand how to make KMP work in real projects. We'll cover the core ideas, compare KMP with alternatives, walk through a repeatable setup process, and highlight common pitfalls so you can avoid them.
The Problem: Why Cross-Platform Development Still Hurts
Cross-platform development has long been a trade-off between code sharing and platform fidelity. Early solutions like web views sacrificed user experience, while later frameworks like React Native introduced complex bridges. The core problem remains: each platform has its own UI paradigms, APIs, and lifecycle expectations. Sharing business logic is appealing, but sharing UI often leads to apps that feel foreign on one platform.
The Cost of Duplication
When teams maintain separate iOS and Android codebases, they duplicate not just business logic but also testing, bug fixes, and feature development. A typical feature might require two implementations, two sets of unit tests, and two code reviews. This duplication slows velocity and increases the chance of inconsistencies. KMP addresses this by allowing you to write shared code in Kotlin while still using native UIs—but only if you approach it correctly.
Common Misconceptions About KMP
One common misconception is that KMP is a write-once-run-anywhere solution. In reality, KMP focuses on sharing business logic, data models, networking, and validation—not UI. Another is that KMP is only for Android developers. While Kotlin is the language, iOS developers can consume shared Kotlin code via Objective-C or Swift bindings, and web developers can use Kotlin/JS. The learning curve exists, but it's manageable for teams willing to invest.
Many industry surveys suggest that teams adopting KMP report reduced development time for shared features, but they also note that initial setup and platform-specific integration require careful planning. The key is to start small: share only the most stable, platform-agnostic logic first, then expand gradually.
Core Frameworks: How KMP Works Under the Hood
To use KMP effectively, you need to understand its compilation model and how it interacts with each platform. KMP compiles Kotlin source code into different targets: JVM bytecode for Android, native binaries for iOS via Kotlin/Native, and JavaScript for web via Kotlin/JS. This multi-target compilation is what enables code sharing, but it also introduces constraints.
The Source Set Model
KMP projects organize code into source sets: commonMain for shared code, and platform-specific sets like androidMain and iosMain. The common set contains code that compiles for all targets, while platform-specific sets hold implementations that depend on platform APIs. This separation forces you to think about which parts of your code are truly cross-platform and which need native treatment.
For example, you might define an interface in commonMain for a data repository, then provide platform-specific implementations in androidMain (using Room) and iosMain (using CoreData). The compiler ensures that platform-specific code is only available on the correct target, preventing accidental cross-platform dependencies.
Expect/Actual Declarations
KMP uses the expect and actual keywords to declare platform-dependent APIs. You define an expected function or class in commonMain, then provide actual implementations in each platform source set. This mechanism is essential for accessing platform-specific features like file I/O, networking, or UI. However, overusing expect/actual can lead to fragmented codebases—use it sparingly and prefer abstracting behind interfaces when possible.
Another important concept is the Kotlin Multiplatform plugin for Gradle, which manages target configurations. You can declare multiple targets (e.g., androidTarget, iosArm64, iosSimulatorArm64) and configure dependencies per source set. The plugin also handles linking and packaging of native libraries for iOS.
Execution: A Repeatable Workflow for Starting a KMP Project
Setting up a KMP project from scratch can be daunting, but a structured workflow reduces friction. Here's a step-by-step approach that we've seen work well in practice.
Step 1: Define Your Sharing Boundaries
Before writing code, decide what to share. Start with data models, networking (using Ktor), serialization (kotlinx.serialization), and validation logic. These are typically platform-agnostic and provide immediate value. Avoid sharing UI or platform-specific APIs initially—those can be added later as you gain confidence.
Step 2: Initialize the Project
Use the Kotlin Multiplatform wizard (available at kmp.jetbrains.com) or a template from the official GitHub repository. Select your targets (e.g., Android, iOS, and optionally web or desktop). The wizard generates a Gradle project with the necessary plugins and source sets. For iOS, you'll need to configure the Xcode integration manually or use a tool like CocoaPods or Swift Package Manager.
Step 3: Implement Shared Logic
Write your shared code in commonMain. Use Ktor for HTTP requests, kotlinx.serialization for JSON parsing, and coroutines for asynchronous operations. Test this code thoroughly using common tests (run on all targets). For platform-specific dependencies, use expect/actual or dependency injection libraries like Koin to provide implementations.
Step 4: Integrate with Platform UIs
On Android, consume shared code directly via Kotlin. On iOS, you'll generate a framework (using the KMP Gradle plugin) that can be imported into Xcode. Use Swift or Objective-C to call shared functions. This step often requires creating wrappers to handle Kotlin's nullable types and coroutines.
Step 5: Set Up CI/CD
Configure continuous integration to build and test all targets. For iOS, this means running on macOS agents. Use Gradle tasks like linkDebugFrameworkIosArm64 to generate the iOS framework, and integrate with Fastlane or Xcode Cloud for deployment.
One team I read about started by sharing only the data layer for a social media app—user profiles, feed fetching, and caching. They reported a 30% reduction in development time for new features after the first month, as both iOS and Android teams could work on the same data logic.
Tools, Stack, and Maintenance Realities
Choosing the right tools and understanding maintenance costs are crucial for long-term success with KMP.
Essential Libraries and Plugins
The KMP ecosystem includes several well-maintained libraries: Ktor for networking, SQLDelight for local databases, kotlinx.serialization for JSON, and Koin or Kotlin-inject for dependency injection. For testing, use kotlin.test and mock libraries like MockK. For UI, Compose Multiplatform is emerging, but it's still maturing—consider it for prototypes or simple interfaces, but not for production apps with complex native interactions.
Build Configuration Complexity
Gradle configuration is one of the biggest hurdles. You need to manage multiple targets, dependencies per source set, and platform-specific settings. Using version catalogs and convention plugins can help. Also, be aware that iOS builds require macOS and may take longer due to native compilation. Some teams use remote build agents to speed up iOS builds.
Maintenance Overhead
Maintaining a KMP project means keeping up with Kotlin version updates, library compatibility, and platform SDK changes. Each platform update (e.g., new iOS API) may require changes in platform-specific source sets. The community is active, but documentation can lag behind. Plan for regular dependency updates and allocate time for migration.
Compared to Flutter and React Native, KMP offers more native control but with higher setup complexity. Flutter provides a unified UI framework, while React Native uses JavaScript bridges. KMP's advantage is that you write business logic once in Kotlin, a statically typed language, which reduces runtime errors. The trade-off is that you still need to maintain two UIs (or use Compose Multiplatform, which is less mature).
| Framework | Shared Logic | Shared UI | Performance | Learning Curve |
|---|---|---|---|---|
| KMP | High (Kotlin) | Low (Compose Multiplatform) | Native | Medium-High |
| Flutter | Medium (Dart) | High | Good (Skia) | Medium |
| React Native | Medium (JavaScript) | High | Fair (Bridge) | Low-Medium |
Growth Mechanics: Scaling KMP Across Teams and Projects
Once you have a working KMP module, scaling it across multiple projects or teams requires planning. This section covers modularization, library publication, and team onboarding.
Modularization Strategies
Organize shared code into feature modules or core modules. For example, create a shared:network module for networking, shared:domain for business logic, and shared:data for repositories. Each module can be a separate Gradle subproject with its own source sets. This promotes reusability and parallel development.
Publishing Shared Libraries
If your organization has multiple apps, consider publishing shared KMP libraries to a private Maven repository. This allows teams to consume shared modules without duplicating source code. Use Gradle's maven-publish plugin to publish artifacts for all targets. Note that iOS frameworks need to be published as XCFrameworks, which can be distributed via CocoaPods or Swift Package Manager.
Onboarding New Team Members
The learning curve for KMP includes understanding Gradle, source sets, and platform-specific integration. Create onboarding documentation that covers common patterns, such as how to add a new API client or how to handle platform-specific serialization. Pair experienced Kotlin developers with iOS developers to bridge the knowledge gap. Many teams find that a two-week spike is enough to build a minimal viable shared module.
A composite scenario we've seen: a mid-sized company with three mobile teams—Android, iOS, and a shared services team—adopted KMP for their core data layer. They started with a single module for user authentication, then gradually added profiles, feeds, and messaging. Within six months, they had reduced duplicate code by 40% and accelerated feature delivery by 25%.
Risks, Pitfalls, and Mitigations
KMP is not without risks. Understanding these pitfalls early can save your project from common failures.
Pitfall 1: Over-Sharing Business Logic
Not all logic benefits from sharing. Platform-specific behaviors—like UI state management, animations, or sensor data—should remain native. Forcing them into shared code leads to complex expect/actual declarations and fragile abstractions. Mitigation: define clear boundaries. Share only what is truly cross-platform, and use interfaces to abstract platform dependencies.
Pitfall 2: Ignoring Platform Conventions
Each platform has its own design patterns (e.g., MVVM on Android, MVC on iOS). Trying to force a shared architecture can result in apps that feel unnatural on one platform. Mitigation: keep UI logic in platform-specific code and use shared code only for business logic. Consider using platform-specific navigation patterns.
Pitfall 3: Underestimating Build Complexity
Gradle configuration for multiple targets can become unwieldy. Common issues include dependency conflicts, missing native libraries, and slow build times. Mitigation: use Gradle version catalogs, apply convention plugins, and invest in a CI pipeline that caches dependencies. For iOS, consider using a dedicated build machine.
Pitfall 4: Neglecting Testing
Shared code should be tested on all targets to catch platform-specific bugs. However, mocking platform APIs can be challenging. Mitigation: write unit tests for common code using kotlin.test, and use integration tests for platform-specific parts. Consider using a testing library like Ktor Mock Engine for network code.
Pitfall 5: Lack of Community Support for Niche Libraries
While popular libraries like Ktor and SQLDelight are well-maintained, niche libraries may not support KMP. This can force you to write custom implementations or fall back to platform-specific code. Mitigation: evaluate library support before committing. For missing functionality, consider wrapping platform libraries using expect/actual.
Decision Checklist: Is KMP Right for Your Project?
Before starting a KMP project, run through this checklist. If you answer yes to most questions, KMP is likely a good fit.
Project Characteristics
- Do you have two or more target platforms (e.g., Android and iOS)?
- Is your business logic relatively stable and independent of UI?
- Do you have a team with Kotlin experience or willingness to learn?
- Is your app's UI heavily customized per platform (not a simple form-based app)?
- Do you have the infrastructure to build and test iOS (macOS CI)?
Team Readiness
- Can you dedicate 2-4 weeks for initial setup and learning?
- Do you have buy-in from both Android and iOS developers?
- Is your organization willing to maintain a shared codebase with platform-specific code?
When to Avoid KMP
KMP may not be suitable if your app is primarily UI-driven with minimal business logic, or if you need to ship quickly with a small team. In such cases, Flutter or React Native might be faster. Also, if your iOS team is not comfortable with Kotlin or Gradle, the learning curve may slow you down. Finally, if your app relies heavily on platform-specific APIs (e.g., ARKit, CameraX), KMP's expect/actual overhead may outweigh the benefits.
We recommend starting with a small proof of concept—share one feature's data layer—and measure the impact before committing fully.
Synthesis and Next Actions
Kotlin Multiplatform is a powerful tool for modern professionals who want to streamline development without sacrificing platform quality. The key takeaways from this guide are:
- Start small: share only stable, platform-agnostic business logic.
- Use
expect/actualsparingly; prefer interfaces and dependency injection. - Invest in build configuration and CI from day one.
- Test shared code on all targets.
- Keep UI native; avoid forcing shared architectures.
Your next steps: identify a candidate feature in your current project, set up a minimal KMP module using the JetBrains wizard, and implement the data layer. Run common tests and integrate with both Android and iOS UIs. Measure the time saved versus a native-only approach. If the results are positive, expand to additional features.
Remember that KMP is still evolving. Stay updated with the official Kotlin blog and community forums. The investment in learning KMP pays off as your codebase grows and you need to maintain consistency across platforms. By following the practical steps and avoiding common pitfalls outlined here, you can make KMP a valuable part of your development toolkit.
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!