> ## 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.

# Duet vs Flutter: Native UI vs One Codebase

> Duet and Flutter both ship iOS and Android from shared code. They differ on who draws the screen, what ships in the binary, and what proves the apps agree.

Flutter ships one codebase whose framework draws every pixel on both platforms; Duet ships two native interfaces over one shared, compiled logic core. Pick Flutter to write everything — interface included — once, in Dart; pick **Duet**, Modaal's cross-platform parity framework for native iOS and Android apps, when the interface must be the platform's own and the thing you share is behavior. This page compares the two models on the same three questions the [Duet overview](/articles/duet#how-duet-compares-to-other-cross-platform-approaches) asks of every approach.

*Last reviewed: September 1, 2026. Flutter version and capability claims were checked against Flutter's official documentation on this date.*

## How do Duet and Flutter differ?

<Frame>
  |                                          | Flutter                                                                                                                                                                                                                                               | Duet                                                                                                                                  |
  | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
  | Who draws the screen                     | Flutter's own renderer — the Impeller canvas — on both platforms; widgets belong to the framework                                                                                                                                                     | Each platform's native toolkit: SwiftUI on iOS, Jetpack Compose on Android                                                            |
  | Where feature logic lives                | Once, in Dart, running on the Dart runtime the app ships                                                                                                                                                                                              | Once, in Kotlin, compiled into both apps as a library                                                                                 |
  | What proves the two platforms agree      | One implementation — there is nothing to compare, except code behind platform channels                                                                                                                                                                | Recorded fixtures replayed on both platforms; CI compares byte-for-byte                                                               |
  | Languages in the project                 | Dart (plus per-platform code at the boundary)                                                                                                                                                                                                         | Kotlin for logic, Swift and Kotlin for the two interfaces                                                                             |
  | The interface toolkit                    | Flutter's widget catalog, Material and Cupertino                                                                                                                                                                                                      | Apple's and Google's own — HIG on one side, Material 3 on the other                                                                   |
  | Customizing beyond the catalog           | Theming covers Flutter's own widgets; a control that must be the platform's own is a platform view with a Swift/Kotlin side, and anything outside the canvas is platform code                                                                         | Every control is already the platform's own; customization is ordinary SwiftUI and Compose code                                       |
  | A native, perf-heavy island              | Drawing-heavy screens stay in Dart (painters, GLSL shaders); a camera, video or GPU island is a `Texture` or a platform view fed by Swift/Kotlin plugin code, with its logic outside the shared Dart                                                  | Ordinary SwiftUI/UIKit and Compose/Android code, no bridge; the reducer decides, a worker runs the pipeline, and the seam is recorded |
  | Native code you write and compile for it | Yes: Swift or Objective-C in the iOS host project and Kotlin or Java in the Android one, compiled by Xcode and Gradle inside the Flutter build; a pub.dev plugin spares the writing, not the compiling                                                | Yes, by design: Swift compiled by Xcode, Kotlin compiled by Gradle                                                                    |
  | SwiftUI and Compose in the island        | Yes, wrapped: an iOS platform view returns a `UIView`, so SwiftUI enters through `UIHostingController`; an Android platform view returns a `View`, so Compose enters through `ComposeView`; a full-screen Jetpack Compose activity is also documented | They are the interface: SwiftUI on iOS, Jetpack Compose on Android, by construction                                                   |
</Frame>

## Who draws the screen?

Flutter draws every pixel itself. Since Flutter 3.44 the [Impeller renderer](https://docs.flutter.dev/perf/impeller) is the only engine on iOS, the default on Android API 29+ and, as of Flutter 3.47 (August 2026), the default on desktop; only the web target still renders with Skia. The widgets are Flutter's own — the Material catalog, plus Cupertino widgets that imitate Apple's controls. That buys one interface implementation and pixel-identical screens on both platforms; it also means the app's look is the framework's, and a new OS design language or control appears in your app when the framework implements it, not when the OS ships.

The catalog is broad, its theming is deep — one `ThemeData` sets colors and type for every Material widget in the app — and customization *inside* the canvas is cheap, because Flutter owns the pixels and a bespoke widget is Dart. Customization *toward the platform* is the expensive direction. The Cupertino widgets are re-implementations with the parameters the framework exposes; a control that must be the platform's own — a native map, a web view, a camera preview — is a [platform view](https://docs.flutter.dev/platform-integration/ios/platform-views), `UiKitView` on the Dart side and a `FlutterPlatformView` factory in Swift or Objective-C on the other, and Flutter's documentation states that platform views "come with performance trade-offs". Anything outside the canvas — a home-screen widget, a share extension — is platform code regardless. Per-platform variation therefore costs what a platform channel costs: the control is written per platform, plus the bridge.

Duet renders nothing itself. The iOS app is SwiftUI, the Android app is Jetpack Compose, and each inherits its platform's controls, typography, accessibility behavior and OS-version look natively. The cost is symmetric: those two interface layers are [written twice](/articles/duet#what-duet-requires-of-you).

## What ships in the app binary?

A Flutter release build ships the Dart runtime alongside your ahead-of-time-compiled Dart code. That runtime is well-engineered and the sizes are documented — Flutter's own FAQ measured a minimal release APK at about 4.3 MB (ARM32) and a minimal IPA download at about 10.9 MB, figures the FAQ dates to March 2021 — but it is a second language runtime in the binary, and every screen and every rule runs on it.

Duet's shared core is Kotlin compiled to machine code — Kotlin/Native for the iOS app, JVM bytecode on Android's own ART like any Android library. There is no bundled language VM or interpreter; the compiled core adds about 3 MB to the Android app, measured on the shipped app Modaal's Duet pages quote. The interface layers are ordinary platform code with no framework runtime under them.

## What proves the two platforms agree?

Flutter's answer is identity by construction: one implementation means there is nothing to diverge — until the code crosses a [platform channel](https://docs.flutter.dev/platform-integration/platform-channels). Camera, health data, background execution, a vendor SDK: that code is written per platform in Swift/Objective-C and Kotlin/Java (Pigeon generates the type-safe bridge), and behavior on the two sides of the channel is once again kept equal by review and QA rather than by construction.

Duet's answer is identity by verification: each feature's behavior is recorded as fixture files, both apps replay them, and CI compares the replay byte-for-byte — in the adoption measurement, 6 of 6 recordings were byte-identical across the Swift/Kotlin boundary. The guarantee covers exactly what the fixtures cover: state, actions, requested effects and their ordering. The two interface layers sit outside it by design, which is the same boundary honesty Flutter's platform channels need — stated up front in both cases.

## What if one screen must be native and fast?

Split the case in two. A screen Flutter can draw — a keyframe-animated illustration, a custom-rendered chart, a shader effect — stays in Dart: Flutter owns the canvas, and [custom fragment shaders](https://docs.flutter.dev/ui/design/graphics/fragment-shaders) (GLSL, supported by both Skia and Impeller) and custom painters are ordinary Flutter code. No escape, and the screen is shared.

An island that must be the platform's — a camera preview, hardware-decoded video, a Metal or ARKit view, an audio pipeline — leaves Dart on day one. Flutter's two routes are the [`Texture`](https://api.flutter.dev/flutter/widgets/Texture-class.html) widget, "a rectangle upon which a backend texture is mapped", fed by "a plugin that integrates with host platform video player, camera, or OpenGL APIs", and a platform view, which the docs say "come with performance trade-offs". Either way the island is written in Swift and Kotlin behind a plugin, and its processing logic — capture settings, frame filters, encoding — sits outside the shared Dart (or reaches C through [`dart:ffi`](https://docs.flutter.dev/platform-integration/ios/c-interop)), kept equal across platforms by review rather than by construction. The drawback is two native implementations plus the channel that carries their parameters and results, inside a screen the framework otherwise owns.

**Do you write and compile native code for it?** Yes, unless an existing plugin covers the island exactly. The platform side is Swift or Objective-C in the `ios/` host project and Kotlin or Java in `android/`, compiled by Xcode and Gradle as part of the Flutter build. A plugin from pub.dev spares you the writing, not the compiling: its native source is built into your app the same way. A C library reached through `dart:ffi` is one more thing to build or vendor per platform.

**Can the island be SwiftUI or Compose?** Yes, wrapped. A platform view is a `UIView` on iOS and an `android.view.View` on Android, so a SwiftUI view arrives through [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller), "a UIKit view controller that manages a SwiftUI view hierarchy", and Compose through [`ComposeView`](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views), which "is an Android `View`". Flutter's [Android platform-views page](https://docs.flutter.dev/platform-integration/android/platform-views) documents the compositing modes and their trade-offs, and a separate guide covers [launching a full-screen Jetpack Compose activity](https://docs.flutter.dev/platform-integration/android/compose-activity), where "you will only write Kotlin code". Either way the SwiftUI or Compose code is yours to write, and the host project compiles it.

In a Duet app the island is ordinary platform code — AVFoundation and Metal in the SwiftUI app, CameraX and MediaCodec in the Compose app — with nothing between it and the OS. The [reducer](/articles/duet-glossary#reducer) decides when capture starts, which filter applies and what happens with the result; a [worker](/articles/duet-glossary#worker) runs the pipeline. What is recorded and byte-compared is that seam: the requests the reducer makes and the results that come back. The pipeline's internals are outside the verified surface, and the island is written twice, once per platform: Swift compiled by Xcode, Kotlin compiled by Gradle, by design.

## What does Flutter do better?

* **One implementation of everything.** Interface included. A screen is built once, looks the same on both platforms, and there is no second view layer to write or review.
* **The iteration loop.** Stateful hot reload is mature and covers every target — including the web, where hot reload has been stable since Flutter 3.35 (August 2025). Duet's loop is compile-and-run per platform, plus a recording step when behavior changes.
* **Package ecosystem.** pub.dev lists roughly 77,000 packages (September 2026), many wrapping both platforms behind one Dart API.
* **Reach beyond the two phone platforms.** Stable desktop targets (with Canonical named lead maintainer of Flutter Desktop in May 2026) and a web target from the same codebase. Duet targets iOS and Android only.
* **Add-to-app.** Embedding a Flutter module into an existing native app is an officially supported, documented path.

## When should you pick which?

Pick Flutter for UI-neutral business applications — forms, lists, dashboards, internal tools, MVPs — whose screens look the same on both platforms and need little per-platform customization, and when one team writing Dart end-to-end outweighs native fidelity. The fit weakens as the interface must vary by platform: each control that has to be the platform's own is a platform view with a Swift and a Kotlin side, so a product that keeps asking for them pays for two native interfaces and the framework. Pick Duet when your users expect each platform's own behavior, when the interface must track the OS (new controls, accessibility features, design-language shifts) on release day, or when "do both apps do the same thing?" needs a mechanical answer because the domain is rules-dense — money, health, data.

One framing note, so this page implies no benchmark that never ran: Modaal builds native-first — both apps use their platform's own toolkit — and that constraint was fixed before any framework evaluation. Flutter was ruled out by the constraint, not by a bake-off, and this page compares the models on their documented properties.

## Common questions

<AccordionGroup>
  <Accordion title="Doesn't Cupertino make Flutter apps feel native on iOS?" icon="apple">
    Cupertino widgets imitate Apple's controls on Flutter's canvas; they are re-implementations, not the platform's controls. They track iOS closely, but new OS controls and behaviors arrive when the framework implements them, and system behaviors attached to real UIKit/SwiftUI views come from the imitation, not the OS. Whether that difference matters is a product judgment — this page's position is stated above.
  </Accordion>

  <Accordion title="Is Dart a disadvantage?" icon="code">
    Dart is a sound, garbage-collected language with excellent tooling, and treating it as a defect would be wrong. The trade is staffing and reach: Dart expertise is mostly Flutter expertise, while Duet's languages are the platforms' own — Kotlin and Swift — which iOS and Android engineers already write.
  </Accordion>

  <Accordion title="Can Flutter share logic with a native UI, like Duet does?" icon="share-nodes">
    Add-to-app embeds Flutter screens into a native app, but the shared code still runs on the shipped Dart runtime, and driving two fully native interfaces from a Dart core is not the framework's design. The share-the-logic route with a compiled core is Kotlin Multiplatform or Rust territory — the survey page maps the options.
  </Accordion>

  <Accordion title="Does Duet have hot reload?" icon="arrows-rotate">
    No. The interface layers use each platform's own preview tooling (SwiftUI previews, Compose previews), and logic changes re-run the recorded scenarios — a 1–3 second warm suite at four-feature scale in the field measurement. It is a different loop: slower to see pixels, faster to prove behavior.
  </Accordion>

  <Accordion title="Which is better for AI coding agents?" icon="robot">
    Flutter gives an agent one language and a huge training corpus. Duet gives an agent machine-checkable gates: a recorded scenario must pass before a screen can wire to logic, and CI replays the corpus on every push, so a wrong edit fails mechanically instead of compounding. The iOS architecture comparison scores this axis across architectures.
  </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

* [Flutter documentation](https://docs.flutter.dev) and the [Flutter FAQ](https://docs.flutter.dev/resources/faq) — rendering model, app-size figures, governance
* [Impeller rendering engine](https://docs.flutter.dev/perf/impeller) — engine status per platform
* [Platform channels](https://docs.flutter.dev/platform-integration/platform-channels) and [Add-to-app](https://docs.flutter.dev/add-to-app) — the native boundary and embedding paths
* [Hot reload](https://docs.flutter.dev/tools/hot-reload) — scope and targets
* [Fragment shaders](https://docs.flutter.dev/ui/design/graphics/fragment-shaders), the [`Texture` widget](https://api.flutter.dev/flutter/widgets/Texture-class.html), [platform views](https://docs.flutter.dev/platform-integration/ios/platform-views) and [C interop](https://docs.flutter.dev/platform-integration/ios/c-interop) — the in-canvas and escape-to-native routes for a heavy screen
* [Android platform views](https://docs.flutter.dev/platform-integration/android/platform-views) and [launching a Jetpack Compose activity](https://docs.flutter.dev/platform-integration/android/compose-activity); Apple's [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) and Android's [`ComposeView`](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/compose-in-views) — how SwiftUI and Compose enter a platform view
* [The Duet framework on GitHub](https://github.com/modaal-agent/duet) — both flavors, the recording toolchain and the versioned contracts

## Read next

<CardGroup cols={2}>
  <Card title="The best cross-platform frameworks in 2026" icon="map" href="/articles/best-cross-platform-framework">
    The full survey: every mainstream framework on the same three questions, plus the quadrant this pair sits in.
  </Card>

  <Card title="Duet vs Compose Multiplatform" icon="clone" href="/articles/duet-vs-compose-multiplatform">
    The nearest relative on the canvas side: same language decision as Duet, opposite UI decision.
  </Card>

  <Card title="Should you share code between iOS and Android?" icon="share-nodes" href="/articles/share-code-ios-android">
    The decision one level up: write twice, share everything, or share the logic.
  </Card>

  <Card title="Duet overview" icon="mobile-screen-button" href="/articles/duet">
    What Duet is, the wizard cards that scaffold it, and the six-step loop every feature follows.
  </Card>
</CardGroup>
