Mobile Engineering

Kotlin Multiplatform in 2026: the shared logic, native UI playbook (and when shared UI backfires)

Kotlin Multiplatform is now widely treated as production-ready for sharing business logic across Android and iOS, but the hardest decisions are still architectural: what to share, how to package it for iOS, and when shared UI becomes a liability rather than leverage.

You are not deciding whether Kotlin Multiplatform (KMP) “works” in 2026. You are deciding which parts of your app you want to standardise across platforms, and which parts must stay native to avoid slowing delivery.

Google’s Android guidance now explicitly positions KMP as stable and production-ready for sharing business logic between Android and iOS (updated 26 August 2026). (developer.android.google.cn) JetBrains, meanwhile, has been pushing Compose Multiplatform on iOS as stable/production-ready since 2025, with ongoing iOS-focused improvements landing in 2026 releases. (blog.jetbrains.com) The debate you’re hearing is not “can we share?” but “where does sharing pay off, and where does it create new failure modes?”

The decision: share a layer, not a slogan

The most reliable way to ship with KMP is to treat it as a shared core: networking, domain logic, and selected storage and integration code, while keeping SwiftUI and Jetpack Compose (Android) native.

If you try to answer this as “KMP vs React Native vs Flutter”, you’ll miss the practical question that matters to a team of 20–500 people: what do we want to be identical, and what do we want to be idiomatic?

A simple framing that tends to hold up in production:

  • Share what is expensive to re-implement and easy to validate (business rules, API contracts, offline state machines).
  • Keep native what is expensive to debug cross-platform and changes with OS conventions (UI, accessibility, platform navigation patterns).
  • Treat “shared UI” as an opt-in for specific product shapes, not as a default.

A layer-by-layer framework (what to share, why, and how it fails)

Networking and API clients (usually: share)

What you get:

  • One set of models, serializers, auth flows, retry/backoff policies.
  • One place to implement API evolution and feature flags.

Common failure modes:

  • Over-sharing “platform concerns” such as certificate pinning, background execution, reachability, or deep OS integrations.
  • Ignoring iOS-specific constraints (for example: background task lifecycles) and then discovering your “shared” client needs platform code anyway.

Rule of thumb: share the protocol-level behaviour; keep platform lifecycle hooks and OS integrations in platform modules.

Domain layer (almost always: share)

This is where KMP is at its best: deterministic logic with test coverage and minimal UI coupling.

Share:

  • Use-cases / interactors.
  • Validation and formatting rules that must be consistent.
  • State reducers (if you run a unidirectional state architecture).

Failure mode: turning the shared module into a “god module” with every dependency pulled in “because it’s convenient”. Once the shared module becomes the place where everything lives, the iOS side starts paying in build times, awkward interop, and poor debuggability.

Storage and offline (share selectively)

You can share significant pieces here, but be explicit about boundaries.

Share:

  • Caching rules and eviction policy.
  • Repository orchestration (when to read cache vs network).

Keep platform:

  • Keychain / Keystore wrappers.
  • OS-specific file locations and protections.

Failure modes:

  • Trying to enforce identical persistence primitives on both platforms (you’ll end up fighting platform tooling and performance debugging).
  • Making iOS depend on Android-ish concepts (e.g. assuming background workers behave the same).

UI (the fork in the road)

There are now credible teams shipping Compose Multiplatform UI to iOS, and JetBrains has explicitly claimed iOS is stable/production-ready (with continuous iOS improvements in subsequent releases). (blog.jetbrains.com) That does not mean “shared UI is the default best choice”. It means it is an option when the product and team shape make the trade-off worthwhile.

When shared UI can be a win:

  • You have a design system that is already implemented in Compose on Android.
  • You want pixel-consistent UI across platforms (e.g. media apps, internal tools, B2B workflows).
  • Your team is Kotlin-heavy and iOS capacity is a bottleneck.

When shared UI tends to backfire:

  • You need iOS-native interaction patterns quickly (text input nuances, accessibility behaviour, platform navigation edge-cases).
  • You are heavily invested in SwiftUI patterns, iOS testing infrastructure, and UIKit bridges.
  • You are building a consumer product where “feels native” is a competitive edge.

The failure mode is rarely “it crashes”. It’s that UI work becomes slower because:

  • Debugging and profiling crosses language/tooling boundaries.
  • Your iOS engineers become framework integrators rather than product builders.
  • You start shipping platform-specific UI exceptions anyway, defeating the point.

A pragmatic compromise that works well: keep SwiftUI/Compose native, but share presentation logic (state, view models, formatting) behind a stable interface.

The “shared logic, native UI” module layout that stays sane

Your goal is to keep the shared artefact small, stable, and easy for iOS to consume.

A structure that tends to scale:

  • :shared:core (pure Kotlin: domain models, use cases, validation)
  • :shared:data (API clients, repositories, caching orchestration)
  • :shared:platform (expect/actual boundaries, platform adapters)
  • :androidApp (Compose UI, Android wiring)
  • :iosApp (SwiftUI UI, iOS wiring)

Key design constraints:

  • Keep platform-specific integrations behind interfaces in shared code.
  • Keep the iOS-facing surface area deliberately small.
  • Prefer fewer, stable entry points over “everything is public”.

“Done” here is not “we moved code to KMP”. Done is:

  • Android and iOS both ship features from the same domain logic with minimal duplication.
  • The iOS app consumes a versioned artefact (not a pile of build scripts everyone fears touching).
  • Platform teams can still move fast independently in UI.

iOS interop in 2026: treat packaging as a product

Most KMP programmes get stuck not on Kotlin, but on iOS integration friction: how the shared code is delivered, versioned, and upgraded.

JetBrains’ Kotlin Multiplatform documentation outlines iOS integration options and explicitly calls out Swift Package Manager (SwiftPM) and CocoaPods as ways to connect an iOS framework from a KMP project. (kotlinlang.org) In 2026, SwiftPM is increasingly the “default expectation” for iOS teams, and Kotlin Multiplatform provides guidance on exporting an XCFramework that can be consumed via SwiftPM. (kotlinlang.org)

Practical guidelines for iOS interop:

  • Export a single, well-named XCFramework (avoid multiple overlapping frameworks unless you have a strong reason).
  • Keep your Swift-facing API small and stable (fewer generics, fewer Kotlin-specific types leaking out).
  • Version the artefact and publish it like any other dependency.

SwiftPM export vs CocoaPods

If your iOS team is already on SwiftPM, meet them where they are.

  • Kotlin docs show how to set up SwiftPM export using a binary target pointing to a zipped XCFramework. (kotlinlang.org)
  • Kotlin also documents SwiftPM-based workflows for local integration that reduce the “rebuild everything” pain compared to direct integration. (kotlinlang.org)

Failure mode to avoid: “The shared module is in the same repo, so we’ll just wire it in however.” That tends to rot into bespoke scripts, fragile Xcode build phases, and an integration that only one engineer understands.

Stay inside known compatibility lanes

Treat toolchain versions as a first-class constraint. Kotlin’s compatibility guidance explicitly calls out that you should check compatibility between the Kotlin Multiplatform Gradle plugin (Kotlin version), Gradle, Xcode, and Android Gradle Plugin. (kotlinlang.org)

This matters because most KMP programme delays look like “we lost a week to toolchain drift”. Don’t make it a surprise.

A readiness checklist: what “done” looks like for a real team

If you keep SwiftUI/Compose native, success is measurable. Use this as a go/no-go and as a definition of done.

  1. The shared module has clear boundaries: - No UI code. - Domain is pure and testable. - Platform integrations live behind expect/actual or adapters.
  1. iOS consumption is boring: - One dependency pulled in via SwiftPM (or CocoaPods, if that’s your reality). - A documented upgrade process. - CI builds the artefact and runs tests on every change.
  1. Ownership is explicit: - One “shared core” codeowner group. - One iOS interop owner (this is a real role, not a part-time chore).
  1. Debugging is planned: - Logging strategy that works on both platforms. - A reproducible way to debug shared code on iOS (not just “print and hope”).
  1. Product teams feel the benefit: - A feature ships on both platforms with shared domain logic. - Platform UI teams remain autonomous.

If you cannot make these true in the first 6–10 weeks, you are likely building a cross-platform platform team, not a product.

When shared UI is the right bet (and how to avoid regret)

If you are considering Compose Multiplatform UI on iOS, decide it on product shape and team shape, not ideology.

Green flags:

  • The app is workflow-heavy and the UI is mostly “forms, lists, charts”.
  • You have a strong Compose investment already.
  • You are willing to accept that the iOS app is, in practice, a Kotlin app running on iOS.

Red flags:

  • You need iOS-first polish, accessibility nuance, or OS-consistent behaviours as a differentiator.
  • Your iOS team expects SwiftUI-first patterns and tooling.

A safe starting strategy:

  • Phase 1: shared networking + domain + state, native UI.
  • Phase 2: share some presentation logic and UI components only if the team is stable and the integration is boring.
  • Phase 3: consider shared UI for specific surfaces (admin screens, onboarding experiments, internal tools) before you bet the entire product.
Share UI when it makes delivery faster for the next 12 months, not when it makes the architecture look neat.

How we’d approach it at codeversols (one honest paragraph)

At codeversols we tend to start with a thin, well-tested KMP core and treat iOS interop as a deliverable, not an afterthought: a stable artefact, a versioning story, and clear module boundaries. If you later decide to adopt shared UI, it should be because the product and team structure benefit from it, not because you need to justify the initial KMP decision.

Close

In 2026, KMP is a credible way to reduce duplication without forcing a lowest-common-denominator app. Google’s guidance is unambiguous about KMP being stable/production-ready for shared business logic, and JetBrains continues to push Compose Multiplatform on iOS as production-ready. (developer.android.google.cn) The practical win comes from being strict about boundaries: share what must be consistent, keep UI native unless you have strong reasons, and make iOS packaging and toolchain compatibility part of “done” from day one.

More From Our Blog

Android 16 (API 36) edge-to-edge is enforced: the window insets contract your UI must satisfy
Mobile Engineering • Oct 16, 2026

Android 16 (API 36) edge-to-edge is enforced: the window insets contract your UI must satisfy

Targeting Android 16 (API 36) removes the practical opt-out from enforced edge-to-edge. The only stable fix is to define a single owner for each inset (status bars, navigation bars, display cutout, IME) and test for double-application in CI.

Read more →
TestFlight ‘Waiting for Review’ for days? Ship anyway with a dual‑lane release pipeline (and what to automate in App Store Connect)
Mobile Engineering • Oct 15, 2026

TestFlight ‘Waiting for Review’ for days? Ship anyway with a dual‑lane release pipeline (and what to automate in App Store Connect)

When TestFlight Beta App Review and App Store Review queues stall, you don’t need to miss your launch date. This playbook describes a dual‑lane pipeline (internal TestFlight vs external TestFlight vs App Store), deterministic rollout rules, and the App Store Connect automation to keep shipping under review latency.

Read more →
Swift 6 strict concurrency default isolation footguns: how teams accidentally @MainActor their whole app (and a CI check to prove it)
iOS / Swift • Oct 14, 2026

Swift 6 strict concurrency default isolation footguns: how teams accidentally @MainActor their whole app (and a CI check to prove it)

Swift 6’s strict concurrency checks get teams over the line, but one build setting can quietly turn large parts of your app into MainActor-only code. Here’s how that happens, how it shows up as UI jank, and a simple CI audit that catches “main-actor creep” early.

Read more →