How do Duet and Flutter differ?
Who draws the screen?
Flutter draws every pixel itself. Since Flutter 3.44 the Impeller renderer 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 — oneThemeData 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, 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.
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. 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 (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 theTexture 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), 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, “a UIKit view controller that manages a SwiftUI view hierarchy”, and Compose through ComposeView, which “is an Android View”. Flutter’s Android platform-views page documents the compositing modes and their trade-offs, and a separate guide covers launching a full-screen Jetpack 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 decides when capture starts, which filter applies and what happens with the result; a 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
Doesn't Cupertino make Flutter apps feel native on iOS?
Doesn't Cupertino make Flutter apps feel native on iOS?
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.
Is Dart a disadvantage?
Is Dart a disadvantage?
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.
Does Duet have hot reload?
Does Duet have hot reload?
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.
Which is better for AI coding agents?
Which is better for AI coding agents?
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.
Duet projects are scaffolded by Modaal — starting a Duet project walks through the two template cards.
Sources and further reading
- Flutter documentation and the Flutter FAQ — rendering model, app-size figures, governance
- Impeller rendering engine — engine status per platform
- Platform channels and Add-to-app — the native boundary and embedding paths
- Hot reload — scope and targets
- Fragment shaders, the
Texturewidget, platform views and C interop — the in-canvas and escape-to-native routes for a heavy screen - Android platform views and launching a Jetpack Compose activity; Apple’s
UIHostingControllerand Android’sComposeView— how SwiftUI and Compose enter a platform view - The Duet framework on GitHub — both flavors, the recording toolchain and the versioned contracts
Read next
The best cross-platform frameworks in 2026
The full survey: every mainstream framework on the same three questions, plus the quadrant this pair sits in.
Duet vs Compose Multiplatform
The nearest relative on the canvas side: same language decision as Duet, opposite UI decision.
Should you share code between iOS and Android?
The decision one level up: write twice, share everything, or share the logic.
Duet overview
What Duet is, the wizard cards that scaffold it, and the six-step loop every feature follows.