Engineering

AI Can Write Your App Twice. That’s the Problem.

AI agents can now write idiomatic Kotlin and Swift. That makes the native-vs-cross-platform debate more important, not less, and the answer depends on what kind of team you are.

Key Takeaways
  1. Why the old argument is dead
  2. Team personas, not user personas
  3. Where AI agents actually help
  4. Eleven teams, one decision
  5. Why shared logic still wins
  6. The counterarguments worth taking seriously
  7. Where native still wins
  8. A decision framework
  9. The stack question is an org-design question

‍

Why the old ‘cost’ argument is obsolete

For a decade, the case for cross-platform frameworks was simple arithmetic. Two platforms meant two codebases, two teams, and twice the cost. Flutter, React Native, and Xamarin before them promised to cut that bill in half.

AI coding agents have since  broken that arithmetic. An agent can now take a feature spec and produce a reasonable Jetpack Compose screen and a reasonable SwiftUI screen in the time it takes to review one of them. If writing code twice is nearly free, the original argument for cross-platform looks dead.

The fact is that writing the code has always been only a small part of what it costs to build software. The expensive part is owning it: reviewing it, testing it, debugging it at 2am, and keeping two implementations in agreement every time a requirement changes. AI made generation cheap. It did nothing for verification. In fact, it made verification the bottleneck.

AI can write your app twice. It can’t make sure both versions agree.

That’s the argument of this post. I’ll make it through a lens borrowed from design: personas. Not user personas, but team personas, because what kind of team you are should decide your stack far more than any framework benchmark.

A disclosure up front. I’ve spent 12+ years building Android and cross-platform apps, so when I end up recommending Kotlin Multiplatform for shared logic, you’re entitled to be suspicious. I’ll try to earn the conclusion by arguing the other side harder than its own advocates usually do, and by showing where my own recommendation loses.

Team personas, not user personas

Designers don’t argue about whether a button should be blue in the abstract. They ask who the user is: a commuter checking a ride on a crowded platform, a first-time investor nervous about hitting “Buy”. The persona makes the trade-offs discussable.

The stack debate has never had that discipline. We argue about native vs cross-platform as if there were one right answer for everyone. Users mostly can’t tell which stack you picked, as long as you picked it well. What varies wildly is the team: how many engineers it has, whether they’re split by platform, where its correctness risk lives, and how much code it can realistically review.

So instead of asking “which stack is best?”, this post asks “which stack fits this team?” Every persona below is described along the same five axes.

Two-column table defining the five axes used to describe each team persona: client-side logic density, UI differentiation, team topology, platform depth, and verification capacity, each paired with the question it asks.
The five axes used to describe every team persona

‍

The last axis is the new one. It barely mattered when humans wrote every line, because writing speed and review speed were roughly matched. With agents, a team can generate far more code than it can meaningfully verify, and that gap changes the answer for almost every persona.

Where AI agents actually help

Agents are excellent at the work that used to justify cross-platform frameworks, and much weaker at the work that actually causes production incidents.

Two-column table contrasting what AI agents are strong at, such as boilerplate, repetitive UI, Kotlin-to-Swift translation, and test scaffolding, with what they are weak at, such as architecture, state management, edge cases, and keeping two implementations consistent.
Agents are strong at producing code and weak at guaranteeing it is correct

‍

The left column is about producing code. The right column is about being correct. Agents move the left column close to zero cost, which means the right column is now where nearly all engineering effort goes.

Here is what that looks like when a single business rule changes, say a new fee cap on transfers.

Side-by-side flowcharts of one fee-cap rule change. Left: agents edit Kotlin and Swift separately, with two reviews ending at a “Do they agree?” check. Right: shared KMP logic gets one edit and one review, and both apps pick it up.
One rule change, two workflows

‍

On the left, the question “do they agree?” has no automated answer. Two reviewers each approve code that looks right in isolation. On the right, agreement isn’t a question at all, because there is only one implementation.

Agents do help KMP too. They’re good at generating expect/actual declarations, Swift-friendly wrappers, and interop boilerplate, which used to be KMP’s biggest tax. One honest caveat: models have seen far more idiomatic Swift and Android Kotlin than KMP-specific code, so expect the occasional confident mistake around multiplatform tooling.

Eleven teams, one decision

Here are eleven teams facing the same question, side by side. Shared logic in KMP wins in roughly half of them, and I think that’s the most important finding in this post.

Table of eleven team personas, from ride-hailing to super-app, with columns for team shape, where the logic lives, the recommended approach, and what would flip it. Recommendations range from Flutter or React Native to native UI with shared KMP logic.
Eleven team personas and the approach that fits each

‍

Six of these are worth walking through in detail.

1. The global ride-hailing platform

Maps, background location, battery tuning, and live trip updates are platform-deep problems, so native UI is non-negotiable. Uber has written publicly about going native and building its own architecture framework (RIBs) to manage scale.

The shared-logic opportunity is narrow but valuable: the trip state machine (requested, matched, arriving, in trip, completed, disputed) and its offline and reconnect behaviour. A trip that shows “arriving” on iOS and “matched” on Android is exactly the kind of silent divergence this post is about.

The twist is that rider and driver apps are different personas. In markets like India, drivers are overwhelmingly on Android and use the app as a work tool. The platform mix of your users is a persona input most teams forget.

2. The solo entrepreneur

This is where “AI writes native twice” is most tempting and most dangerous. An agent will happily generate a SwiftUI app and a Compose app. But one person can meaningfully review and maintain roughly one codebase. Verification capacity is the binding constraint.

Pick one codebase: React Native if you come from the web, Flutter otherwise, KMP with Compose Multiplatform if you already think in Kotlin. Also ask whether a PWA would validate the idea faster.

3. The Neobank

This is the flagship case for shared logic. Fee calculations, transaction validation, KYC flow rules, offline transaction queues, and cryptographic signing all run on the device, and each has regulatory weight. Two implementations of a fee rule means two audits and two chances to be wrong.

Share the logic in KMP with one test suite, and keep the UI native: biometric prompts, trust signals, and accessibility are platform territory. Cash App is a well-known public example of sharing Kotlin across platforms. Nubank went the other way with Flutter, and that’s defensible when UI consistency and team scaling matter more than platform feel.

4. The quick-commerce app in India

The user base is huge, mostly on Android, often on budget phones with patchy networks. App size and cold-start time are business metrics, so native Android performance is critical.

The team is usually asymmetric, with a much bigger Android team. That makes KMP organisationally natural: the Android team already writes the logic, and sharing cart and pricing-display rules multiplies a smaller iOS team. Merchandising surfaces change daily, so they belong in server-driven UI anyway.

5. The enterprise field-service app

Inspectors, logistics drivers, and insurance assessors work offline for hours with complex forms, validation, and sync conflict resolution. Nobody cares whether the app feels iOS-native. Everybody cares whether the data is right.

This is the one persona where I’d share the UI as well, with Compose Multiplatform. It’s a useful stress test of my own “logic yes, UI no” rule: when native feel doesn’t matter, sharing UI is fine.

6. The legacy bank with two native apps

Ten years of Swift and Kotlin, two platform teams of 30 people each, and a long history of “why does iOS show a different balance?” tickets. This is the most common real-world situation, and the one most blog posts ignore.

Don’t rewrite anything. Extract logic into KMP one module at a time, starting with the module that has the worst divergence history. Staff it as a shared-logic team with engineers from both platforms, not as an Android-owned library the iOS team is told to consume.

Why shared logic still wins

The usual claim is that most bugs live in business logic. I don’t think that’s provable, and crash dashboards often say otherwise: lifecycle, concurrency, and memory issues are loud and frequent. The better claim is about cost and visibility.

UI bugs are loud. Someone sees a broken layout and files a ticket within hours. Logic bugs are silent. The wrong fee is charged, a sync conflict resolves differently on iOS, or a validation rule lets bad data through, and nobody notices until it has reached production, spread, and corrupted data or trust.

Two correct-looking implementations can disagree

When an agent writes the same rule in Kotlin and in Swift, each version can look idiomatic and pass its own review while behaving differently. The languages themselves make different default choices.

Table of four scenarios where Kotlin and Swift make different default choices: character counting, integer overflow, rounding a 2.345 fee, and decoding JSON with a missing field. A final column shows what users see when the platforms disagree.
Four places where Kotlin and Swift quietly disagree

‍

None of these is a bug that a reviewer would catch by reading one file. Each implementation is correct by its own platform’s conventions. The bug only exists in the gap between them.

One source of truth, one test suite

With KMP, the rule exists once and its tests exist once. Both apps consume the same compiled module, and the same commonTest suite runs on the JVM and on iOS targets.

Layered architecture diagram. Android UI in Jetpack Compose and iOS UI in SwiftUI sit on top. Both feed one shared KMP module for rules, validation, and state, which connects to commonTest and a shared data layer.
Native UI on top, shared logic and tests underneath

The UI layers stay fully native and platform-owned. Everything below them is written, reviewed, and tested once:

// commonTest: runs against both the Android and iOS builds

class TransferFeeTest {

    @Test

    fun feeIsCappedAtTheLimit() {

        assertEquals(Money("25.00"), TransferFee.calculate(Money("10000.00")))

    }

‍

    @Test

    fun roundingIsHalfEvenEverywhere() {

        assertEquals(Money("2.34"), TransferFee.round(Money("2.345")))

    }

}

This changes three things:

  • Correctness is proven once. A rule that passes commonTest passes on both platforms, because it is the same code.
  • Changes propagate atomically. A new fee cap is one pull request and one review, with no chance that only one platform got updated.
  • Review effort halves where it matters most. Reviewers spend their limited attention on the logic that can quietly cost money, not on reading it twice.

The important scoping: this matters in proportion to how much logic truly runs on the client. For a thin client over a smart backend, there may be little worth sharing.

The counterarguments worth taking seriously

Four objections come up every time I make this case. Each one is partly right.

1. “Just put the logic on the server”

This is the strongest objection. If you want a single source of truth, a thin client with logic behind an API gives you one without any multiplatform tooling. For many consumer apps, like the D2C retail persona, it’s the correct answer.

It stops working when logic has to run on the device: offline-first workflows, sync and conflict resolution, latency-sensitive validation, on-device calculation, encryption and signing, and complex local state machines. Those are exactly the personas where shared logic won in the table above.

2. “Share the spec and the tests, not the code”

A team can keep one written spec plus a set of JSON test fixtures (inputs and expected outputs), let agents generate native Kotlin and Swift, and run both against the same fixtures. That’s a legitimate middle path, and it’s often the politically easiest first step for a brownfield team.

The weakness is that it creates four artifacts that can drift: the spec, the fixtures, and two test harnesses. KMP collapses that into one compiled source that the build enforces.

Comparison table of three approaches: AI writes native twice, shared spec plus fixtures, and shared logic in KMP. Rows compare implementations per rule, test suites, what catches divergence, whether iOS writes Swift logic, and tooling overhead.
Three ways to keep app consistent

3. “Why not share the UI too?”

Now that Compose Multiplatform is stable on iOS, this is a fair question. My reasons for keeping UI native in most personas: platform feel and conventions matter to users, accessibility and system integration are platform-deep, iOS engineers should own the iOS experience, and UI is the part agents regenerate most cheaply. When none of those apply, as with field-service apps, share the UI too.

4. “KMP has real costs”

It does. Swift interop around coroutines, Flows, sealed classes, and generics still needs care. Build times grow, debugging Kotlin from Xcode is less comfortable, and binary size needs watching.

The biggest cost is organisational. KMP often becomes “the Android team owns the logic and iOS consumes a framework it didn’t write.” That’s a recipe for resentment and slow adoption. It’s also why the final section of this post is about org design rather than technology.

Where native still wins

Fully native, with nothing shared, is the right call more often than KMP advocates admit. It wins in four situations:

  • The feel is the product. Camera apps, creative tools, premium consumer social apps, and anything competing on motion and polish. Every abstraction layer is a tax on the thing users are paying for.
  • The app lives in platform APIs. Widgets, Live Activities, watch apps, background execution, media pipelines, on-device ML, deep HealthKit or Health Connect use. When most of your code is platform integration, there is little left to share.
  • The team is two strong specialist teams and the client is thin. If the logic sits on the server and both platform teams are excellent, shared code adds coordination cost for little correctness gain.
  • You only need one platform. A rugged-Android fleet, an iPad-only enterprise tool, or a driver app in an Android-dominant market. The best cross-platform strategy for one platform is none.

Agents make this option cheaper than ever. A strong native team with good agents can ship both platforms quickly. The trade-off doesn’t disappear, though: the moment real logic moves onto the device, the divergence risk from the previous section comes back.

A decision framework

The personas are reduced to a short sequence of questions. Answer them in order, because the first one does most of the work.

Decision tree of four questions: does logic run on the device, is the UI the product, can you verify two codebases, and does native feel matter. Outcomes: native with nothing shared, Flutter or React Native, KMP with Compose Multiplatform, or native UI with shared KMP logic.
Four questions, answered in order

‍

The heatmap below scores every persona on the five axes. Read it left to right: the first two columns, client logic and cost of an error, predict where shared logic pays off. The right-hand columns predict how much of the UI should stay native.

Heatmap scoring eleven personas from 1 to 5 on client logic, cost of an error, team split, platform depth, and UI differentiation. Neobank and field service score 5 on both of the first two columns, which predict where shared logic pays off.
Eleven personas scored on five axes (author's assessment)

Three patterns stand out:

  • Client-side logic density is the strongest predictor. Every persona where shared logic won strongly (neobank, field service, health, legacy bank) scores 4 or 5 on both client logic and cost of an error.
  • Verification capacity is the axis AI added. The solo founder can generate two native apps but can’t verify them. The neobank can generate them but can’t afford to audit two. Both conclusions come from the same constraint.
  • Your users’ platform mix belongs in the framework. Ride-hailing drivers and Indian quick-commerce shoppers show that the answer can be asymmetric: native-first on the platform most of your users are on.

A quick self-check before choosing:

  • We can name the specific business rules that must behave identically on both platforms.
  • We know whether those rules run on the device or the server.
  • We have estimated how much generated code we can review well each sprint.
  • We have agreed who owns shared code, and it isn’t only the Android team.
  • We have checked where our users are: Android, iOS, or both.

The stack question is an org-design question

When code was expensive, choosing a stack was a budgeting decision. Now that agents have made code cheap, the scarce resources are different: correctness, shared understanding, and the attention of the people who review what gets shipped.

Seen that way, choosing KMP (or not) is really a decision about where your team’s single source of truth lives and who owns it. A shared module owned by one platform team is a political problem wearing a technical label. A shared module owned jointly, reviewed by both platforms, and tested once is an organisational advantage that no amount of generated code can replicate.

So the next time someone asks whether to go native or cross-platform, don’t start with frameworks. Start with three questions: what kind of team are we, where does our logic run, and how much can we actually verify?

AI can write your app twice. Deciding that it should only be written once, in the places where being wrong is expensive, is still an engineering judgment.

‍

Originally posted on "Anmol Verma" on October 16, 2026.

‍

Try Ayrin

We work through the AI-native vs. AI-enabled decision with teams directly

About the Author

About the Authors

Anmol is a Principal Engineer with 12+ years crafting cross-platform mobile experiences, specializing in Kotlin Multiplatform. He recently built scalable, elegant systems for the fintech, POS systems and writes on architecture and engineering craft.

Anmol Verma, Principal Engineer, Ayrin Digital

Anmol Verma

Principal Engineer
Read more by this author

Shubham has four years of experience crafting polished, high-performance apps in Flutter and Kotlin Multiplatform Compose. He's active in the developer community, writing technical deep-dives on Medium covering topics like building Compose Multiplatform libraries and migrating codebases from Java Poet to KSP.

Shubham Singh, Mobile Developer, Ayrin

Shubham Singh

Senior Mobile Engineer
Read more by this author

FAQ

No items found.