> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modaal.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Should You Share Code Between iOS and Android?

> Three ways to ship iOS and Android: write two apps, share everything, or share the logic and keep the UI native — with costs, returns, and who should skip it.

Share the business logic when you ship both platforms and your product's value is in its behavior — rules, flows, money, data. Keep the interface native on each platform, because the interface is where the platforms genuinely differ. The rest of this page is the case for that split: the three ways to ship two apps, what each way can prove about behavior, what sharing costs in practice, and the products for which sharing is the wrong call.

## What are the three ways to ship two apps?

Every iOS-and-Android product takes one of three routes:

1. **Write both apps in full.** A Swift codebase and a Kotlin codebase, each implementing every rule. Two teams (or one team twice), full platform fidelity, and every behavior change made twice.
2. **Share everything, including the interface.** One implementation in one language — [Flutter](/articles/duet-vs-flutter), [React Native](/articles/duet-vs-react-native), or [Compose Multiplatform](/articles/duet-vs-compose-multiplatform) — with the framework supplying the widgets on both platforms.
3. **Share the logic, keep the interface native.** One implementation of the rules and flows; two thin interface layers, each in its platform's own toolkit. Google [supports Kotlin Multiplatform for exactly this split](https://developer.android.com/kotlin/multiplatform), and it is the route Duet takes — **Duet** is Modaal's cross-platform parity framework for native iOS and Android apps.

The third route is established practice, not an experiment: Forbes, McDonald's, Cash App and Google Docs ship shared Kotlin logic under native interfaces, per [JetBrains' case studies](https://kotlinlang.org/case-studies/) and [Google's Kotlin Multiplatform page](https://developer.android.com/kotlin/multiplatform), and the same shape exists with a Rust core (Mozilla's UniFFI bindings, Signal's libsignal).

## How do you know both apps behave the same?

The three routes give three different answers, and the answer is the real decision:

* **Identity by convention** — two full codebases. The same rule is implemented twice, and agreement rests on code review and manual QA finding every divergence. Nothing mechanical fails when the two apps drift apart.
* **Identity by construction** — one shared implementation. There is nothing to compare, so agreement is automatic — up to the boundary. Where the shared code calls into per-platform code (a platform channel, a native module), the guarantee stops, and that boundary code is back to identity by convention.
* **Identity by verification** — two implementations (or one shared core consumed twice) checked against the same executable contract. In Duet, each feature's behavior is recorded as fixture files; both apps replay them and [CI compares the results byte-for-byte](/articles/duet#what-you-get), so a divergence is a failing build rather than a bug report.

<Frame caption="Who draws the screen × where the logic runs. The bottom-left cell is the share-the-logic route; the survey page walks every placement.">
  <img src="https://mintcdn.com/modaal/uQZW84DTPYSQL62R/images/cross-platform-quadrant.svg?fit=max&auto=format&n=uQZW84DTPYSQL62R&q=85&s=e3ba64aa64ad834e638de407ccf49173" width="800" alt="Two-by-two grid placing cross-platform approaches by who draws the screen (platform toolkit vs framework renderer) and where logic runs (shipped runtime vs compiled). React Native, .NET MAUI and NativeScript top-left; Flutter and Ionic top-right; Compose Multiplatform bottom-right; Duet, two native apps, KMP shared-core practice, Rust cores and Skip bottom-left." data-path="images/cross-platform-quadrant.svg" />
</Frame>

[The best cross-platform frameworks in 2026](/articles/best-cross-platform-framework) places every mainstream option on this grid, one comparison column per approach.

## What does "business logic" mean concretely?

Business logic is the set of decisions that must not differ between platforms: what a tap is allowed to do, what happens when a payment fails mid-flow, in which order results land, what gets persisted and when. In a Duet feature it is literally one [pure reducer](/articles/duet-glossary#reducer) — current state and an action in, new state and requested effects out — with clocks, network and storage behind effects that platform [workers](/articles/duet-glossary#worker) perform.

Pixels are not business logic. Screen layout, navigation chrome, animation, haptics and typography are where iPhone and Android users hold different expectations — Apple's Human Interface Guidelines on one side, Material 3 on the other — and where sharing produces an app that feels foreign on at least one platform. The split holds even when a screen is data-driven: *what is on screen* is a value in shared state; *how it is rendered* stays per platform. [Handling platform-specific UI](/articles/cross-platform-ui-parity) covers where that line sits case by case.

## What does sharing the logic cost?

Two costs, stated plainly:

* **The views are written twice.** Each feature gets a SwiftUI screen and a Jetpack Compose screen. The logic, navigation decisions, effect handling and the test corpus are written once, so the second view layer is rendering work against behavior that already passes.
* **A change touching both platforms costs more than one platform.** In Modaal's own runs, a behavior change shipped to both apps measured roughly 40% more agent effort than the single-platform equivalent — not the 100% of doing everything twice, and the delta is concentrated in the second view layer.

What sharing does not cost: platform access. The shared core compiles into each app as an ordinary library; anything the platform can do, the app can still do, because the interface layer and the workers are native code. That includes the screens that must be native and fast — a Metal-drawn view, a camera pipeline, a video editor. In the share-the-logic route they are ordinary platform code, with the shared core deciding when they start and what they return; in the share-everything route each one is an escape through the framework's native bridge, written per platform anyway in Swift and Kotlin that someone writes and the app build compiles. [The survey page](/articles/best-cross-platform-framework#which-approaches-escape-to-native-for-a-heavy-screen) scores every approach on that escape.

## What do you get for it?

* **One bug, fixed once.** A logic defect lives in one implementation. The fix ships to both stores from one edit.
* **Behavior parity you can point at.** The recorded fixtures are an artifact — replayed on both platforms on every push, byte-compared in CI. "Do the apps agree?" has a mechanical answer, not an opinion.
* **Native look and feel on both platforms.** Each app uses its platform's toolkit, inherits its accessibility behavior, and adopts new OS releases on the platform's schedule — there is no cross-platform widget layer to wait on.
* **An exit that costs nothing.** Both apps are ordinary native codebases in one repository; the shared core is a library either app could in principle replace. Nothing is hostage to a framework's runtime.

## Who should not share code?

* **Single-platform products.** If Android (or iOS) is genuinely never coming, the shared core's discipline still buys fast, deterministic tests, but the parity machinery is overhead you chose without the payoff.
* **Games and heavily drawn interfaces.** A physics loop or a custom-rendered playfield is not reducer-shaped work, and the drawn surface *is* the product — a shared canvas framework or a game engine fits better. Modaal's own guidance says the same: its 2D game template is not a Duet template.
* **Teams committed to two independent native teams.** Sharing logic couples the teams' release trains at the core. If your organization deliberately staffs two autonomous teams and accepts double implementation as the price of independence, that is a coherent position — it is identity by convention, and manual QA is its verification budget.

## Common questions

<AccordionGroup>
  <Accordion title="Isn't 'write once, run anywhere' cheaper than any of this?" icon="coins">
    A single shared implementation is the cheapest way to get two app icons on two stores, and for interface-light products it is a good trade — the survey page's best-fit row says so. The costs arrive later and are specific: the interface is the framework's on both platforms, and shared code that crosses into per-platform modules leaves the guarantee. Compare the routes on those terms, not on launch cost alone.
  </Accordion>

  <Accordion title="How much code actually gets shared?" icon="percent">
    In shared-logic practice, the logic majority: Forbes states 80%+ shared logic for its KMP apps. In a Duet project, each feature's state, actions, reducer, effects and test corpus are shared; the two view layers and the platform workers are not. The number depends on how interface-heavy the product is.
  </Accordion>

  <Accordion title="Does the shared core lock us to a vendor?" icon="lock-open">
    The shared core is Kotlin compiled into two ordinary native apps — a library, not a runtime. Both codebases open in Xcode and Android Studio and ship to the stores like any native app. That is different in kind from a shared-UI framework, whose widgets and runtime the app cannot ship without.
  </Accordion>

  <Accordion title="Can we start iOS-only and share later?" icon="arrow-right-arrow-left">
    Yes, and it is cheaper if the iOS app records its behavior from day one. Duet's iPhone-first flavor does exactly that: features are recorded and specced as they are built, so the later Kotlin core is written against an executable specification instead of reverse-engineered. The Duet overview covers the per-feature migration.
  </Accordion>

  <Accordion title="What about the web?" icon="globe">
    Sharing logic with a web app is the same decision one platform wider, and Kotlin and Rust cores both compile for it. Duet targets iOS and Android; if web-plus-mobile from one codebase is the requirement today, that is a real advantage of Flutter and Compose Multiplatform — see the survey page's platform-coverage section.
  </Accordion>
</AccordionGroup>

<Note>
  Duet projects are scaffolded by [Modaal](https://modaal.dev) — [starting a Duet project](/articles/duet#starting-a-duet-project) walks through the two template cards.
</Note>

## Sources and further reading

* [Kotlin Multiplatform on developer.android.com](https://developer.android.com/kotlin/multiplatform) — Google's support statement for sharing business logic between Android and iOS
* [JetBrains Kotlin Multiplatform case studies](https://kotlinlang.org/case-studies/) — Forbes, McDonald's, Cash App and others on the shared-logic shape
* [Crux](https://github.com/redbadger/crux) and [UniFFI](https://github.com/mozilla/uniffi-rs) — the same shared-core shape with Rust
* [Apple Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/) and [Material 3](https://m3.material.io) — the two design systems a native interface answers to

## Read next

<CardGroup cols={2}>
  <Card title="The best cross-platform frameworks in 2026" icon="map" href="/articles/best-cross-platform-framework">
    Every mainstream option on one axis — who draws, where logic lives, what proves agreement — with a best-fit row.
  </Card>

  <Card title="Duet overview" icon="mobile-screen-button" href="/articles/duet">
    The product behind the third route: one shared core, two native apps, and a CI gate that fails when they disagree.
  </Card>

  <Card title="Handling platform-specific UI" icon="layer-group" href="/articles/cross-platform-ui-parity">
    Where the shared/native line sits on the rendering side, and how deliberate divergence is recorded.
  </Card>

  <Card title="How to build testable Android apps" icon="vial-circle-check" href="/articles/testable-android-apps">
    The same architecture argued from the testing side: pure reducers, effects as data, recorded scenarios.
  </Card>

  <Card title="Duet tutorials" icon="graduation-cap" href="/tutorials/duet">
    The third route in code: nine tutorials build one app in Kotlin, SwiftUI and Compose, with recordings gating both apps.
  </Card>

  <Card title="iOS or Android first?" icon="newspaper" href="https://modaal.dev/blog/ios-or-android-first">
    Which platform to ship first when you are not shipping both, on modaal.dev.
  </Card>
</CardGroup>
