MobileCodeCross Team
Native vs Flutter / React Native in 2026: When a Single Codebase Actually Costs More
A single Flutter or React Native codebase still ships two stores. In 2026 the cost flips when you need native modules, store-specific UX, or performance-sensitive UI. Here's how to tell before you fund the stack.
Mobile
13 min
- Impeller default
- Flutter 3.27+
- New Architecture
- RN 0.76+
- Cost flip
- 3+ OS surfaces
iOS and Android API 29+
JSI, Fabric, TurboModules
When write-once starts costing more
Flutter made Impeller the default renderer on iOS and on Android API 29+ as of the 3.27 release. React Native enabled its New Architecture by default in 0.76. Google Play has required 16 KB memory page-size support for new apps and updates targeting Android 15+ since November 1, 2025. None of those facts make a single codebase free.
The agency pitch is still "write once, ship twice." That is true for lists, forms, account screens, and most CRUD. It stops being true the moment you need a Live Activity, a Home Screen widget, a camera pipeline, Bluetooth, or a payment sheet that has to behave like the store it sits in. This piece is for the founder or operator funding the stack — not a recap of build timelines, App Store rejections, monetization models, or Austin cost and ROI. Those already exist. The question here is narrower: when does the shared codebase cost more than two native ones?
The write-once pitch still sells. The ledger has changed
You are not buying a language. You are buying a split between the screens a Dart or JavaScript team can share and the surfaces only Swift, Kotlin, and the operating system can own. Generic agency decks collapse that split into one slide: "cross-platform is 30–40% cheaper." Sometimes it is. Sometimes you pay for a shared UI and then pay again for every OS feature the framework documents as a plugin, a platform view, or a Turbo Native Module.
The frameworks themselves are honest about this if you read past the homepage. Flutter's architectural overview treats camera and webview as packages that sit on platform plugins, not as widgets the engine paints. React Native's New Architecture docs say enabling Fabric and JSI may not immediately improve performance unless you refactor to use the new capabilities. Those are not slam pieces. They are the vendors telling you where the shared layer ends.
If your year-one product is a settings-heavy utility, a field tool, or an account-and-catalog app, a single codebase is still the cheaper architecture. If year one includes lock-screen presence, widgets, maps that scroll under your chrome, or a custom camera, you are funding a hybrid whether you admit it or not. The expensive mistake is pretending the hybrid is still "one app."
The write-once pitch vs. the 2026 ledger
The pitch
One Dart or JS team
One design system, two listings
Shared business screens
Lists, forms, account, settings
One release train
Until an OS API ships
then you fund ↓then you fund
The ledger
Channels, JSI, or FFI
Typed specs plus two native impls
Widget and Live Activity targets
SwiftUI / Glance, not Flutter widgets
Store billing and SDK mandates
StoreKit, Play Billing, 16 KB NDK
What a single codebase actually shares in 2026
Count the screens that never touch a platform API. That is the shared estate. On a typical funded app it is the majority of pixels and a minority of risk.
- Navigation, lists, search, filters, empty states.
- Account, profile, settings, help, legal.
- Catalog, cart, and most checkout chrome — not the payment authorization itself.
- Content feeds that do not embed a native map, camera, or webview.
- Admin and internal tools that talk to your API and nothing else.
Flutter and React Native are excellent at that estate. Flutter paints its own pixels through Impeller. React Native's Fabric renderer moves layout to C++ and unlocks synchronous measurement that the old `onLayout` path could not guarantee. If that is most of your product, stop shopping for a reason to go native. You will spend the difference on design and backend work that actually moves revenue.
The shared estate does not include anything Apple or Google render outside your process. Live Activities are WidgetKit and SwiftUI, driven by ActivityKit. Home Screen widgets on iOS are WidgetKit. Android widgets are App Widgets or Glance. App Intents run in the system, not in your Dart isolate or JS runtime. Those are extra targets. A "single codebase" that ships them is a monorepo with three compilers.
Where a mobile product actually lives
Store surfaces
Live Activities, widgets, tiles, App Intents
Swift and Kotlin modules
Camera, BLE, billing, maps, sensors
Platform channels, JSI, or dart:ffi
The tax for talking to the OS
Shared Flutter or React Native UI
Lists, forms, account, most CRUD
Shared business logic and API clients
The part every stack can share
The native-module tax Flutter and React Native document themselves
When a plugin does not exist — or exists and is a year behind the OS — you write the integration. That is not a footnote. It is a second product inside the first.
Flutter's path is platform channels or, for C-level code, dart:ffi. Channels serialize messages between Dart and the host. FFI skips the serializer when you can bind a C API. Flutter's own Jetpack guidance says to search pub.dev first, then write FFI or a channel when the plugin is missing — especially in the months after an OS release. You still implement the iOS side and the Android side. Developing packages is official Flutter work, not an escape hatch.
React Native's path is a Turbo Native Module. You write a typed spec (`NativeSomething.ts`), configure Codegen, then implement the generated interface in Kotlin or Java and in Objective-C++ or Swift. The official tutorial's "hello world" is `localStorage` — SharedPreferences on Android, NSUserDefaults on iOS. That is the cheap case. A camera frame pipeline or a BLE state machine is the same ceremony with more native code behind it.
JSI is a real improvement. The New Architecture docs describe how VisionCamera can move ~30 MB frame buffers without JSON-serializing them across the old bridge. Read that the way an operator should: the framework got out of the way so a native module could do native work. You still hired the person who writes that module.
Budget the tax as a product line, not a spike:
- Spec + Codegen or Pigeon types that both platforms agree on.
- Two native implementations, two test surfaces, two failure modes.
- A host engineer who can read Xcode and Android Studio crash logs.
- A pin strategy when the plugin author is slower than Apple or Google.
- A replacement plan for the day the plugin is abandoned.
One custom module is a surcharge. Three is a team shape. At three, you are paying for Flutter or React Native and for native specialists. That is the first place a single codebase costs more than starting native.
The native-module feedback loop
01
OS ships a surface
Widget, intent, camera, billing, privacy API
02
Plugin lags or is missing
pub.dev / npm cannot outrun an SDK mandate
03
You write two native impls
Channels, FFI, or TurboModules
04 · loops
You maintain the glue
Until the next store deadline
Store-specific UX you cannot fake with a shared widget tree
Users do not review your architecture. They review whether the app feels like iOS or like Android. Apple's Human Interface Guidelines and designing for iOS are not optional flavor. Android's Material 3 in Compose is the language Play users already live in. A Cupertino skin on Android, or a Material clone on iPhone, reads as a port.
The expensive surfaces are the ones the store renders for you:
- Live Activities and the Dynamic Island. Apple's HIG is explicit: a Live Activity must support Lock Screen, Dynamic Island, and StandBy layouts, keep actions few, and treat static vs. dynamic data differently. That UI is SwiftUI in a widget extension. Flutter and React Native can *start* an activity through a plugin. They do not *draw* the compact trailing view.
- Widgets and controls. WidgetKit and App Intents are how iOS lets someone complete a task without opening you. Android's widget APIs and Glance are a separate implementation. If widgets are in the pitch deck, you have already left the single-codebase story.
- Payments. Digital goods go through StoreKit and Google Play Billing. The shared UI can show a paywall. The receipt, the sheet, the restore path, and the store review of that path are platform work. Architecture for revenue belongs in the monetization piece; the stack implication is simpler: billing is a native module even when the rest of the app is Dart.
- Platform navigation and system chrome. Predictive back, edge-to-edge, Dynamic Type, Large Content Viewer, and Material You dynamic color are not CSS variables. You either adopt the platform primitive or you spend QA cycles explaining why your custom chrome fights the OS.
Guideline 4.2 still rejects a webview in a trench coat. That is a completeness problem, covered in why apps get rejected, not a stack essay. The related architectural fact: reviewers and users both notice when the only native thing you shipped is the store binary.
Performance-sensitive UI: when Impeller and Fabric still lose
Impeller's job is predictable shader compilation — shaders built ahead of time so a first scroll does not hitch while the GPU compiles. On iOS, Impeller is the only renderer; you cannot switch back to Skia. On Android API 29+ it is default and falls back to OpenGL where Vulkan is missing. That is a strong default for Flutter-drawn UI. It does not make an embedded Android `SurfaceView` free.
Flutter's Android platform-view docs list the tradeoffs in plain language. Hybrid Composition gives you the best fidelity for the Android view and lowers Flutter's frame rate. Texture-layer composition keeps Flutter fast and makes fast-scrolling native content (a webview, a map) janky. Flutter 3.44's Hybrid Composition++ tries to fix the sync tax by letting Android composite Flutter and the native view through Vulkan `SurfaceControl` transactions. It is opt-in. It requires Android API 34+, Impeller, and a Vulkan device. Everyone else falls back to the older modes. iOS platform views are a separate `UiKitView` registration path, including extra work if you adopted `UIScene`.
So the honest performance question is not "is Flutter slow in 2026?" For Flutter-drawn lists, usually no. The question is "how many frames of my core loop are a platform view?" Maps, camera preview, HTML, and some ad SDKs are platform views. One is survivable. A feed that is half platform views is a product you should have built native.
React Native's New Architecture removes the asynchronous bridge and lets JS hold a C++ object through JSI. That is why high-bandwidth native modules became possible. The same landing page warns you not to expect a free performance win after flipping the flag. Compose's own performance guide is the native counterpart: release + R8, baseline profiles, and fewer recompositions. Native is not automatically faster. Native is faster when the work is the platform's work — camera, maps, scroll-linked video, tight gesture loops — and you are not paying a composition tax to embed it.
Plugins, SDKs, and the 16 KB page-size rebuild
Cross-platform engines are native binaries. Flutter's engine and React Native's Hermes / JSI stack are NDK code. Google's rule is blunt: if you use NDK libraries, directly or through an SDK, you must rebuild for 16 KB pages. Since November 1, 2025, Play rejects new apps and updates that target Android 15+ and do not. This is not a Flutter problem or a React Native problem. It is a "your engine and every .so you vendor" problem.
Google publishes the upside they measured on 16 KB devices. Use the numbers as a compliance fact, not as a stack scoreboard.
Google's measured 16 KB page-size gains
Device boot time
8%
Faster boot, as reported by Android
Source: developer.android.com/guide/practices/page-sizes. Required for Play uploads targeting Android 15+ since Nov 1, 2025.
The operator-relevant part is the dependency graph. Every camera kit, maps SDK, ads SDK, and "drop-in" BLE plugin ships native objects. One unaligned `.so` fails the Play check for the whole binary. Native teams feel this as a Gradle flag. Cross-platform teams feel it as "our favorite plugin does not have a 16 KB build yet, so the shared codebase cannot ship." That is a real delay, and it is caused by the plugin ecosystem, not by Dart or JavaScript.
Play's target API requirement moves every year. You meet it from the engine version you ship. It does not decide Flutter versus native. It does decide whether you can afford an abandoned plugin.
Add-to-app and hybrid stacks: the honest middle
You do not have to pick a religion. Flutter's add-to-app path is official: embed a Flutter module in an existing Android, iOS, macOS, or web app, as a hybrid navigation stack or as a partial-screen view. Multi-engine mode keeps instances isolated. Flutter 3.44 can package a module as a Swift package. React Native has lived inside native shells for a decade.
Read the limitations before you sell this as a free lunch. Flutter documents that you cannot pack multiple Flutter libraries into one application, that plugins which do not implement `FlutterPlugin` misbehave when they assume a Flutter `Activity` is always present, and that the Android module is AndroidX-only. Add-to-app is a migration tool and a way to share a settings or catalog island. It is a poor way to hide a camera or a Live Activity inside "the Flutter app."
The hybrid that usually wins for a funded product:
- Native shell for store surfaces, billing, notifications, and anything with a widget extension.
- Shared Flutter or React Native for the large, boring, high-screen-count interior.
- A hard rule: if a screen is a platform view, it is a native screen.
That is more architecture than a template shop wants to sell. It is also how you keep the write-once savings on the screens that actually share, without lighting the OS-surface budget on fire.
When native-first is the cheaper architecture
Native-first means SwiftUI + Jetpack Compose as the UI source of truth, with shared business logic only where it is boring — API clients, analytics events, pricing tables. You pay two UI implementations. You stop paying the composition tax, the plugin pin tax, and the "why does this map scroll like 2019" tax.
Choose native-first when two or more of these are true:
- Year one includes Live Activities, widgets, App Intents, or Android tiles as a user-facing feature, not a nice-to-have.
- The core loop is camera, maps, BLE, audio, or another sensor pipeline.
- You already employ (or can hire) Swift and Kotlin, and you do not have a deep Flutter or React bench.
- You need the week-one OS API, not the week-twelve plugin.
- Store-specific motion and navigation are part of the brand, not a theme toggle.
Choose Flutter or React Native when the opposite is true: the product is screen-count heavy, the native surfaces are one or none, and your hiring pool is Dart or React. React Native is the better cross-platform bet if the same team already ships React on the web and you want one component model. Flutter is the better bet if you want pixel control, a single layout system, and you are willing to own Dart. Neither choice is "more native." Both become native the first time you embed a view.
A shop that quotes "Flutter is always cheaper" without listing your OS surfaces is selling a template. Ask them which of your year-one features are platform views. If they cannot answer, they have not priced the app you are funding.
Illustrative first-year engineering weeks by product type
weeksLive-ops product
26 weeks
Cost flip · Shared UI 8 · Native modules 6 · OS surfaces 8 · Store / SDK 4
- Shared UI
- Native modules
- OS surfaces
- Store / SDK
A decision matrix for founders funding the build
Ignore language preference for one page. Score the product you are actually shipping in the next twelve months.
Stack fit by the surfaces you must ship
Shared business UI
Flutter
Strong
React Native
Strong
Native pair
Extra UI cost
Widgets / Live Activities
Flutter
Native tax
React Native
Native tax
Native pair
First-class
Camera / maps / BLE
Flutter
Views + FFI
React Native
JSI + views
Native pair
Default
| If this is year one | Default stack | Why | What you still budget |
|---|---|---|---|
| CRUD, internal tool, catalog | Flutter or React Native | Shared UI is the product | One billing or push module |
| Marketplace or subscriptions | Either, with a native billing owner | Paywalls share; receipts do not | StoreKit + Play Billing + restore |
| Delivery, fitness, live audio | Native-first or hybrid shell | OS surfaces are the product | Widget + ActivityKit + sensors |
| Camera or maps as the core loop | Native-first | Platform views tax the shared renderer | FFI/JSI only if you already have the bench |
| Existing native app, new interior | Add-to-app | Official Flutter path; RN shells are proven | Plugin assumptions in a host Activity |
If you are on the fence, count load-bearing OS surfaces, not screens. Zero or one: share the UI. Two: hybrid, and write the contract between the shell and the interior before you hire. Three or more: native-first is usually cheaper by month nine, because you stop paying two staff models for the same crash.
How to budget the stack, not just the screens
Price three lines, not one. The Austin ROI piece is about whether an app is the right spend. This is about which compilers that spend buys.
- Shared UI. Screen count × complexity. This is where Flutter and React Native return the money. Do not pad it to hide native work.
- Native modules. One owner per module, two implementations, tests on device. If the estimate says "use a plugin" without naming the plugin and its last OS-support date, treat it as custom.
- OS surfaces. Widget extension, ActivityKit, App Intents, Glance, notification content extensions. These are not "small native files." They are separate executables with their own lifecycle.
Then add the unglamorous line every stack pays: store SDK mandates, privacy manifests, 16 KB alignment, and the next target API. That line does not choose Flutter versus native. It punishes stale plugins harder than it punishes a native repo you own.
Hiring is part of the ledger. A Flutter team that cannot open Xcode is a single point of failure the first time a channel crashes in review. A React Native team that has never written a Turbo Module will discover Codegen during the sprint you reserved for the camera. Native-only teams waste money redrawing settings screens. Match the bench to the surfaces, not to the tweet that said "nobody writes native anymore."
Three products, three cost shapes
01 · Shared-first
CRUD / tools
Write-once is the cheaper architecture
02 · Hybrid
Marketplace
Shared interior, native billing and shell
03 · Native-first
Live-ops
OS surfaces are the product
Next steps: pick the stack against your surfaces
Decide the stack against surfaces, not slogans
01 →
List year-one OS surfaces
Widgets, Live Activities, camera, BLE, maps
02 →
Mark each as plugin or custom
Name the library and its last OS support
03 →
Split the budget in three
Shared UI / modules / store surfaces
04 →
Choose the staff model
Dart, React, or Swift+Kotlin — not "full stack"
05 →
Write the hybrid contract
Or commit to native-first / share-first
06
Revisit after the first OS release
The loop restarts on every SDK
Bring that list to the estimate, not a feature spreadsheet that says "iOS + Android" as if they were one row. We will tell you whether a single codebase is still the cheaper architecture, or whether you are about to fund three compilers and call it one app.
If the surfaces are still fuzzy, start with the product conversation — what the first year actually costs and how long a build takes when the stack is honest. Then pick the compilers.
Directional range in a few questions — not a binding quote.
Ready to price an Austin build?
Bring the problem, the users, and a budget ceiling. We’ll tell you whether an app is the right next spend — and what the first year actually costs.
Prefer writing? Send project details on the contact page.