Skip to main content
Kotlin Multiplatform Projects

Kotlin Multiplatform Projects: A Practical Guide for Modern Professionals to Build Cross-Platform Apps Efficiently

Modern professionals face a familiar dilemma: build native apps for each platform and incur double the maintenance cost, or compromise with a cross-platform framework that may limit performance or user experience. Kotlin Multiplatform (KMP) offers a third path—share business logic across platforms while keeping native UIs. But KMP is not a magic bullet. Many teams dive in expecting seamless code sharing, only to hit unexpected complexity. This guide provides a clear-eyed, practical roadmap for using KMP efficiently, based on common patterns and pitfalls observed across real projects. Why Cross-Platform Development Needs a Rethink The Cost of Separate Codebases For years, the standard approach to mobile development meant maintaining two (or more) independent codebases: one for iOS (Swift/Objective-C) and one for Android (Kotlin/Java). Each platform required separate teams, separate testing, and separate bug fixes.

Modern professionals face a familiar dilemma: build native apps for each platform and incur double the maintenance cost, or compromise with a cross-platform framework that may limit performance or user experience. Kotlin Multiplatform (KMP) offers a third path—share business logic across platforms while keeping native UIs. But KMP is not a magic bullet. Many teams dive in expecting seamless code sharing, only to hit unexpected complexity. This guide provides a clear-eyed, practical roadmap for using KMP efficiently, based on common patterns and pitfalls observed across real projects.

Why Cross-Platform Development Needs a Rethink

The Cost of Separate Codebases

For years, the standard approach to mobile development meant maintaining two (or more) independent codebases: one for iOS (Swift/Objective-C) and one for Android (Kotlin/Java). Each platform required separate teams, separate testing, and separate bug fixes. Features often shipped at different times, and even simple logic changes—like updating a discount calculation—had to be implemented and tested twice. Industry surveys consistently show that this duplication can increase development time by 40–60% and testing effort by even more. For startups and small teams, this overhead can be crippling.

Existing Cross-Platform Solutions and Their Trade-offs

Before KMP, teams turned to frameworks like React Native, Flutter, and Xamarin. Each has strengths, but all share a fundamental trade-off: they replace native UIs with a custom rendering engine or a bridge to JavaScript. This often leads to performance gaps, platform-feature lag, and a feeling of 'almost native.' React Native, for instance, relies on a JavaScript bridge that can cause jank in animations. Flutter renders its own widgets, which look consistent across platforms but may not feel native to users. KMP takes a different approach: share only the logic layer—network, data storage, validation, and business rules—while keeping the UI fully native. This hybrid model promises the best of both worlds, but it requires a clear understanding of what to share and what to keep platform-specific.

When KMP Makes Sense

KMP is not for every project. It shines when you have a substantial amount of business logic that is identical across platforms: data models, API clients, validators, and state management. It is less useful for UI-heavy apps where each screen is highly customized per platform, or for projects that need to ship rapidly with a single codebase for both logic and UI. In the latter case, Flutter or React Native may be more pragmatic. Our focus here is on teams that value native UX and are willing to invest in a shared logic layer.

How Kotlin Multiplatform Works Under the Hood

The Expect/Actual Mechanism

KMP's core innovation is the 'expect/actual' pattern. In the common code (usually a module named shared), you declare an expect function or class. For example, expect fun platformName(): String. Then, in each platform-specific source set (e.g., androidMain, iosMain), you provide an actual implementation. This pattern allows the compiler to resolve the correct implementation for each target at build time. The beauty is that the rest of your code—the business logic—never needs to know which platform it's running on. It just calls the expect declaration. This is similar to dependency injection but enforced at the language level.

Compilation and Interoperability

KMP compiles to platform-specific binaries. On Android, it produces standard JVM bytecode that runs in the Android runtime. On iOS, it compiles to a native framework (via Kotlin/Native) that can be consumed by Swift or Objective-C. This means there is no runtime interpreter or bridge—calls from Swift to KMP code are direct, with minimal overhead. However, the interop layer requires careful handling of memory management (ARC on iOS) and concurrency models. Kotlin/Native uses an automatic memory manager, but it is not identical to Swift's ARC, so developers must understand how to manage object lifetimes across the boundary.

Shared Code Structure

A typical KMP project has a shared module with source sets for each target. The common main source set holds all shared logic. Platform-specific source sets (e.g., iosMain, androidMain) contain actual implementations for platform APIs. You can also have a commonTest source set for unit tests that run on all platforms. This structure encourages clean separation of concerns but requires a disciplined build configuration. Gradle (for Android/JVM) and the Kotlin Multiplatform plugin handle the orchestration. The project also needs a native app target (iOS) that imports the shared framework. This setup can be daunting for teams new to KMP, but once established, it becomes a repeatable pattern.

A Step-by-Step Workflow for Your First KMP Project

Setting Up the Development Environment

Start by installing the latest version of IntelliJ IDEA or Android Studio with the Kotlin Multiplatform plugin. For iOS development, you need Xcode and the Kotlin/Native compiler (usually bundled with the plugin). Create a new project using the KMP wizard (File > New > New Project > Kotlin Multiplatform App). Choose a template—typically 'Mobile Shared Code' or 'Compose Multiplatform' depending on your UI approach. The wizard generates a Gradle build file with the necessary plugins and source sets. For this guide, we assume you will use native UIs (SwiftUI on iOS, Jetpack Compose or XML on Android) and share only the logic.

Defining the Shared Module

In the shared module, define your data models, API client (using Ktor), and repository classes. Use expect/actual for platform-specific dependencies like file storage or sensors. For example, you might have an expect class PlatformContext that provides access to the app's resources. On Android, the actual implementation uses android.content.Context; on iOS, it uses NSBundle. Keep the shared module free of UI code. Test your shared logic using commonTest with a multiplatform testing framework like kotlin.test and a mocking library such as MockK.

Integrating with the iOS App

Build the shared module to produce an iOS framework (usually a .framework or .xcframework). In Xcode, add this framework as a dependency. You may use a Gradle task like embedAndSignAppleFrameworkForXcode to automate the build and embedding. In Swift code, import the framework (e.g., import shared) and call the shared classes. Note that Kotlin's naming conventions (e.g., MyRepository) map to Swift objects; you may need to use @ObjCName annotations for smoother interop. For callbacks, use Kotlin's coroutines or convert them to Swift's async/await via the @ObjCAsync annotation.

Integrating with the Android App

On Android, the shared module is automatically included as a dependency in the app module's Gradle file. You can directly call shared classes from Kotlin code in your Android app. Since both use the JVM, there is no interop friction. This asymmetry (iOS requires a framework build, Android is direct) is a common source of confusion. Ensure your CI/CD pipeline builds the iOS framework for every commit to avoid integration surprises.

Tools, Libraries, and Maintenance Realities

Essential Libraries for KMP

Several libraries have emerged to support KMP development. Ktor is a multiplatform HTTP client that works on Android, iOS, and server. SQLDelight provides multiplatform SQLite database access with compile-time SQL verification. Kotlinx.serialization handles JSON serialization without reflection. For dependency injection, Koin has KMP support. For reactive streams, Kotlinx.coroutines and Flow work across platforms. For UI, Compose Multiplatform allows sharing UI code as well, but it is still maturing. We recommend starting with shared logic only and using native UIs until Compose Multiplatform stabilizes for your target platforms.

Build Configuration and CI/CD

Managing a KMP project requires a robust build configuration. The Gradle build file must declare all targets (android, iosArm64, iosSimulatorArm64, etc.) and configure the framework embedding. A common pitfall is forgetting to add the iOS simulator target, which prevents testing on the simulator. Use Gradle tasks to automate the iOS framework build and integrate it into Xcode. For CI/CD, you need a macOS agent to build the iOS framework. Services like GitHub Actions, Bitrise, or Jenkins can be configured with macOS runners. Cache the Gradle dependencies and the iOS framework to speed up builds.

Maintenance Over Time

KMP evolves rapidly. Library updates, Kotlin version bumps, and plugin changes can break builds. Allocate time for maintenance: updating Kotlin versions, migrating API changes, and testing the shared module on both platforms. The community is active, but documentation can lag. Rely on official Kotlin documentation and the library's changelogs. Consider using version catalogs in Gradle to centralize dependency versions. Regularly run tests on both platforms to catch regressions early. A shared CI pipeline that builds and tests all targets is essential.

Growth Mechanics: Scaling Your KMP Project

Expanding the Shared Code Base

As your app grows, you may be tempted to share more code. Resist the urge to share UI until you have a clear benefit. Focus on sharing domain logic, network calls, persistence, and analytics. For each new feature, ask: 'Is this logic identical across platforms?' If yes, add it to the shared module. If the logic differs even slightly, consider keeping it platform-specific to avoid complex expect/actual branches. Over time, you can extract common patterns into a reusable shared library across multiple projects within your organization.

Team Structure and Onboarding

KMP requires developers who understand both Kotlin and the platform-specific languages (Swift, Java). This is a rare combination. When hiring or training, look for developers with strong Kotlin skills and a willingness to learn iOS development basics. Pair programming between Android and iOS developers can be effective. Create a shared vocabulary: use the same class names and patterns across platforms. Document the expect/actual boundaries clearly. Onboarding new team members should include a walkthrough of the shared module and the build process.

Performance and Optimization

Performance in KMP is generally good, but there are hotspots. The interop layer between Kotlin and Swift can introduce overhead if you pass large data structures frequently. Use value types (data classes) and minimize object creation across the boundary. Profile your app on both platforms using platform-specific tools (Instruments on iOS, Android Profiler). Pay attention to serialization: JSON parsing in Ktor is fast, but consider using Protocol Buffers for high-throughput scenarios. Memory management: Kotlin/Native uses a garbage collector, which can cause pauses. Tune the GC parameters if needed, or use manual memory management for performance-critical paths.

Common Pitfalls and How to Avoid Them

Over-Sharing Business Logic

The most common mistake is trying to share too much. Teams often attempt to share code that has subtle platform differences—like date formatting, localization, or UI state management. This leads to a proliferation of expect/actual declarations that are hard to maintain. Rule of thumb: if the implementation differs in more than three lines per platform, keep it platform-specific. Use interfaces and dependency injection to abstract the differences instead of using expect/actual for every minor variation.

Underestimating Build Configuration

Setting up KMP from scratch can take days. The Gradle configuration is complex, especially for iOS targets. Many teams skip the CI/CD setup for iOS and later struggle with manual builds. Allocate at least a week for the initial setup, including a working CI pipeline. Use the official Kotlin Multiplatform wizard as a starting point, but expect to tweak it. Common issues: missing iOS simulator target, incorrect framework name, and Xcode build phase misconfiguration. Keep a checklist of these items.

Neglecting Testing

Shared code should be tested on all platforms. Unit tests in commonTest run on the JVM, which is fast, but they may miss platform-specific behavior. Use integration tests on actual devices or simulators for critical paths. For iOS, you need to write XCTest cases that call the shared framework. This requires setting up a test target in Xcode that links the shared framework. Many teams skip this and rely solely on Android testing, leading to iOS-specific bugs. Invest in a test matrix that covers both platforms.

Mini-FAQ: Addressing Common Concerns

Is KMP production-ready?

Yes, for shared logic. Major companies like Netflix, McDonald's, and Square have used KMP in production. However, the tooling and library ecosystem are still evolving. Expect breaking changes with major Kotlin releases. For UI sharing via Compose Multiplatform, it is still experimental for iOS. We recommend using KMP for logic only in production and waiting for Compose Multiplatform to reach stable status for iOS.

How steep is the learning curve?

For Android developers familiar with Kotlin, the learning curve is moderate. They need to understand the expect/actual pattern and Gradle configuration. For iOS developers, the curve is steeper because they must learn Kotlin and the KMP build system. Overall, a team can become productive in 2–4 weeks, depending on prior experience.

Can I use KMP with existing native projects?

Yes. You can add a shared module to an existing Android project and then create a new iOS project that consumes the shared framework. This is a common migration path. It allows you to start sharing new features without rewriting existing code. Gradually move business logic from the native codebases into the shared module.

What about performance?

For logic-heavy apps (networking, data processing, business rules), KMP performs on par with native code. The overhead of the interop layer is negligible for most use cases. For UI-heavy apps, the performance depends on the native UI framework you use, not KMP. Avoid sharing performance-critical loops across the interop boundary.

Synthesis and Next Actions

Decision Checklist

Before starting a KMP project, evaluate the following: (1) Do you have a significant amount of business logic that is identical across platforms? (2) Are you willing to invest in a complex build setup? (3) Do you have access to macOS for iOS builds? (4) Can your team handle both Kotlin and platform-specific languages? (5) Is your timeline flexible enough to accommodate learning and setup? If you answered 'yes' to most, KMP is a strong candidate. If not, consider alternative approaches.

First Steps

Start with a small proof of concept: share a single feature like a login screen's validation logic or a network client. Measure the time saved versus maintaining two separate implementations. Use this as a basis for deciding whether to expand. Set up CI/CD early to avoid integration pain. Document your expect/actual boundaries and build configuration. Join the Kotlin Slack community and the official Kotlin forums for support.

Final Thoughts

KMP is not the easiest path to cross-platform development, but for teams that value native UX and have substantial shared logic, it can significantly reduce maintenance overhead. The key is to be disciplined about what you share and to invest in proper setup and testing. Avoid the hype and focus on the practical trade-offs. With careful planning, KMP can be a powerful tool in your development arsenal.

About the Author

Prepared by the editorial contributors at languor.xyz. This guide is written for software architects and team leads evaluating Kotlin Multiplatform for their projects. It synthesizes common patterns and pitfalls observed across multiple teams, reviewed against current Kotlin documentation and community best practices. As the KMP ecosystem evolves, readers should verify library versions and build configurations against official sources. Last reviewed: June 2026.

Share this article:

Comments (0)

No comments yet. Be the first to comment!