Kotlin Multiplatform Is a Sharing Boundary, Not a Write-Once Promise

Android
KotlinKMPMultiplatform

When teams ask whether Kotlin Multiplatform is ready, they often ask the wrong first question: “How much code can we share?”

The more useful question is: “Which parts of this product should behave the same, and which parts should feel native?”

That distinction matters because KMP is not one fixed UI strategy. It lets a team share business logic while keeping Android and iOS interfaces native, or share a much larger part of the UI with Compose Multiplatform. Those are different product and team decisions, even though they sit in the same ecosystem.

Share rules before pixels

In one KMP project, I shared the data models, service calls, repositories, ViewModel and business rules in Kotlin. Android used Jetpack Compose; iOS used SwiftUI. The goal was not to make both apps look identical. It was to stop duplicating behavior that should have stayed consistent.

This is a useful starting point when the platforms need different interaction details, or when a team already has strong native UI skills. A shared state model can keep loading, error and content behavior aligned while each UI uses its platform’s conventions.

The trade-off is an interop boundary. Kotlin state has to be exposed to Swift in a way the iOS app can consume and test. A shared ViewModel does not remove that work; it moves the work to the bridge between Kotlin and Swift. That boundary deserves a small prototype before the team commits to a large migration.

UI sharing is a separate decision

If the team wants to share screens as well as business logic, Compose Multiplatform deserves a serious evaluation. Its iOS target is now documented as stable, so it should not be dismissed solely because it is cross-platform UI. That still does not make it the right choice for every application.

The decision depends on the product’s interaction needs, accessibility requirements, existing Swift expertise, design system, and the cost of maintaining platform-specific exceptions. A content-heavy app with similar layouts may benefit from shared UI. An app whose value depends on deeply platform-specific interactions may prefer native UI with shared Kotlin logic.

Kotlin’s own recommended structures reflect this choice: one shared module may be enough when all clients share UI and logic; separate shared-logic and shared-UI modules can make more sense when one client keeps a native interface. The official project-structure guide describes these options.

Measure duplication removed, not code shared

A high sharing percentage is not a product outcome. A useful KMP boundary reduces duplicated rules, keeps important behavior consistent, and does not make platform-specific work harder than it needs to be.

I would evaluate a pilot with concrete questions:

  • Can the shared logic be tested without launching either app?
  • Can the iOS team inspect and handle shared state without awkward adapters everywhere?
  • Do platform-specific features stay behind clear interfaces?
  • Does a change to a business rule reach both platforms with less coordination?
  • Are build, debugging and release steps still understandable to the whole team?

If the answers are good, expand the shared area gradually. If the shared module becomes a place where every platform exception is hidden, the boundary is probably wrong.

The point of KMP is not to make Android and iOS identical. It is to choose deliberately where they should be consistent, and to leave the rest of the product free to use the platform well.

Further reading