Flutter or Kotlin Multiplatform? Choose by What You Share

The honest comparison: Flutter shares the interface and the logic through its own renderer, Kotlin Multiplatform shares the logic and leaves each platform its native interface. Which costs less depends on what you already have.

The question is usually asked as "which is better", and answered with a feature list. That is not how the decision is actually made. Both ship real apps to millions of users. The difference is what each one shares, and therefore what you still have to write twice.

The one-sentence difference

Flutter draws its own interface with its own rendering engine, so one codebase produces the screens on both platforms. Kotlin Multiplatform shares the parts under the interface — models, networking, storage, business rules — and leaves the screens to Jetpack Compose on Android and SwiftUI on iOS.

Everything else follows from that.

Start from what you already have

A brownfield native app. If you have a working Android app and a working iOS app, Kotlin Multiplatform fits the way you work. You can move one module — the API client, say, or the pricing rules — into shared code and ship it, without a rewrite and without a flag day. Flutter in an existing app is possible, but adding a second rendering engine and a second toolchain to an app that already builds is a large change for a small first win.

A new product with a small team. Flutter is hard to beat here. One codebase, one language, one set of widgets, one build. A team of three can cover both platforms, and the screens look identical because they are the same screens.

A design system that must feel native. If your app lives or dies by feeling like the platform — system controls, platform navigation gestures, accessibility behaviour that matches the OS exactly — native interfaces with shared logic is the safer road. Flutter can get very close, and the gap keeps narrowing, but you are reproducing platform behaviour rather than inheriting it.

What each one costs you

Flutter

  • Anything the framework does not wrap needs a plugin or a platform channel, which means writing Kotlin and Swift anyway — so budget for a developer who can.
  • Your app carries the engine. The size difference matters for some markets and not at all for others; measure it against your own install numbers rather than arguing about it.
  • Your team writes Dart. That is easy to learn and harder to hire for than Kotlin or Swift in most cities.

Kotlin Multiplatform

  • You still build two interfaces. Shared logic typically covers 30 to 60 per cent of the code, not 90.
  • The iOS side needs someone who is comfortable in Xcode and Swift, and who will not be surprised when a Kotlin flow has to be bridged to something SwiftUI can observe.
  • Tooling has improved enormously but is still younger than either native stack on its own; expect to read release notes.

A table you can argue with

If this is trueLean towards
Two native apps already exist and workKotlin Multiplatform
New product, small team, speed matters mostFlutter
Interface must match platform conventions exactlyKotlin Multiplatform
Design is custom and identical on both platformsFlutter
Heavy use of new OS features on day oneKotlin Multiplatform
Team already writes Kotlin and no SwiftEither, but hire for iOS before KMP
Complex business rules that must not differ by platformKotlin Multiplatform

What moving one module actually looks like

The Kotlin Multiplatform path is easier to picture with code. Say both apps compute the fee for a course, and the two implementations have drifted — Android applies the discount before tax, iOS after. Move the rule, and only the rule:

// shared/src/commonMain/kotlin/pricing/Fee.kt
data class Fee(val listPrice: Int, val discountPercent: Int) {
    val payable: Int get() = listPrice - (listPrice * discountPercent / 100)
}

Android uses it as ordinary Kotlin. iOS gets a framework and uses it from Swift:

let fee = Fee(listPrice: 25000, discountPercent: 68)
priceLabel.text = formatter.string(for: fee.payable)

Nothing else changed: both apps keep their own screens, their own navigation and their own release process. That is why the brownfield case is so much stronger for KMP than for Flutter — the first shared module can be a week of work, and if it does not suit you, deleting it is also a week.

The friction appears at the boundary. Kotlin's Flow and suspending functions do not map onto Swift concurrency on their own; teams either wrap them at the edge or use a library that does. Budget for that bridge once, early, rather than discovering it in every feature.

The test that settles it

Pick the hardest screen in your product — the one with the payment flow, the offline cache, or the camera — and build it twice in a week, once each way. Not a to-do list demo: the hard screen. You will learn more about which choice fits your team than any comparison article can tell you, including this one.

Whichever you choose, write the business rules so they do not depend on the framework. Teams that keep their domain logic free of UI types find the second decision, years later, much cheaper than the first one was.