Native vs Flutter vs React Native is a lifecycle decision about which complexity a product wants to own. All three can ship high-quality iOS and Android applications. The meaningful differences appear when a team adopts a new platform API, debugs an OS-specific failure, integrates a vendor SDK, meets accessibility expectations, upgrades dependencies, or changes staffing years after launch.
Do not select from a benchmark, language preference, or promised percentage of shared code. Begin with product constraints and build a representative vertical slice. Price the platform-specific work that remains in every option: signing, store release, permissions, lifecycle, notifications, deep links, privacy declarations, device testing, and incident response. Shared UI code does not remove platform ownership.
Define the product constraints that can eliminate an option
List non-negotiable capabilities for the next three years: newest OS features on launch day, advanced camera or media pipelines, background execution, Bluetooth or accessory protocols, widgets, watch or spatial companions, rich text, complex animation, offline databases, regulated device support, embedded web, and existing native SDKs. Add minimum OS versions, startup and binary budgets, accessibility conformance, and required form factors.
Then map organization constraints: current Kotlin, Swift, Dart, JavaScript, and C++ skills; hiring markets; web-team overlap; number of apps; release independence; native platform leadership; design-system maturity; and tolerance for framework upgrades. A product with ordinary forms and a small unified team has a different optimum from a media app that must expose each platform's newest capture stack immediately.
| Constraint | Native | Flutter | React Native |
|---|---|---|---|
| Immediate access to new platform APIs | Direct, subject to OS availability | May require plugin or platform channel work | May require library or Turbo Native Module work |
| Shared custom UI across iOS and Android | Separate UI implementations | Strong shared rendering and widget model | Shared React components with deliberate platform branches |
| Existing web React organization | New mobile language and platform depth | New Dart and Flutter ecosystem | Familiar React concepts, plus native mobile ownership |
| Heavy vendor-native SDK portfolio | Direct integration path | Plugin quality and native escape hatch matter | Library compatibility and native-module work matter |
| Highly platform-specific experience | Natural fit | Shared code may decrease | Platform files and components may increase |
Understand each runtime and native boundary
Native development uses platform toolchains and UI frameworks directly. Apple positions SwiftUI as a declarative framework across Apple platforms, while Android is now Compose-first. Direct access reduces waiting for a cross-platform abstraction, but product logic and UI are normally implemented twice unless the team deliberately shares a lower-level module. Consistency depends on architecture and design governance rather than one source tree.
Flutter ships a framework and rendering model with Dart, plus platform channels and plugins for native integration. React Native renders through native platform integration and supports JavaScript or TypeScript with native components and modules. Its New Architecture is the default from React Native 0.76, according to the architecture overview. Both cross-platform options retain Xcode, Gradle, signing, manifests, entitlements, and native debugging in the delivery chain.
Measure useful code sharing instead of total shared lines
Classify code as domain logic, networking and persistence, design tokens, reusable UI, platform UI, native integration, release configuration, and tests. Shared business logic can be valuable; shared workarounds are negative leverage. React Native explicitly supports the Platform module and .ios or .android files in its platform-specific code guide. That is a feature, not failure: forced sameness can make both platforms feel wrong.
Track change lead time for a feature on both platforms, defects caused by divergence, and time spent in native bridges or plugins. A 90 percent shared repository can still have its riskiest 10 percent in payments, identity, notifications, and upgrades. Conversely, separate native UIs can share API contracts, product rules, fixtures, visual tokens, analytics definitions, and release evidence even when source code is not shared.
| Cost area | Evidence to collect | Commonly missed cost | Decision signal |
|---|---|---|---|
| Feature delivery | Paired feature cycle time and rework | Platform polish after shared implementation | Stable median across representative work |
| Dependencies | Plugin or library count, owners, release lag | Abandoned transitive package | Critical capability has maintained path |
| Upgrades | Framework, language, OS, toolchain effort | Skipped versions compound migration work | Team can rehearse upgrades quarterly |
| Quality | Device matrix, accessibility, crash diagnosis | One-platform regressions hidden by shared tests | Failures are locally diagnosable |
| People | Hiring, onboarding, on-call coverage | No native expert during incidents | At least two owners for each native boundary |
Prototype the highest-risk vertical slice
Build one production-shaped journey in each viable option, not three polished home screens. Include authentication, deep linking, a native SDK, offline persistence, background/resume behavior, analytics, accessibility, localization, automated tests, release signing, and one failure diagnosis. Run on low-end Android hardware and the oldest supported iPhone as well as current devices. Measure startup, interaction smoothness, memory, binary size, build time, and engineering effort.
Use currently supported toolchains. Flutter's supported-platforms page identifies tested and supported OS ranges for the current documented release; treat those floors as changing inputs, not facts frozen in an architecture deck. For React Native, verify every critical library against the chosen architecture and version. For native, prove that teams can keep product behavior aligned without serial handoffs.
Plan upgrades and ownership before committing
Name owners for framework upgrades, iOS and Android platform changes, dependency review, build infrastructure, and design-system behavior. Define an upgrade cadence and a maximum supported lag. Run candidate upgrades on a branch with the full device and release pipeline, record incompatible packages, and budget replacement work. Cross-platform frameworks can concentrate upgrade effort; native can split it by platform. Neither removes it.
Create escape rules. A cross-platform app may implement a feature natively when accessibility, performance, or API timing requires it. A native portfolio may share a domain library when duplicated rules create material risk. Document when a local choice becomes an architectural exception and who maintains it. Avoid rewrites based on frustration with one feature; migration itself creates a long dual-stack period and rarely fixes weak product boundaries.
Evaluate migration and portfolio effects
For an existing app, compare incremental modernization with framework migration. Inventory native screens, custom views, SDKs, build variants, accessibility debt, automated tests, release automation, and active feature work. A rewrite creates parity work and a long interval where defects and fixes must be managed in two implementations. Use route-by-route or feature-boundary migration only when navigation, state, analytics, and design tokens can cross the boundary predictably.
At portfolio scale, sharing may be more valuable in platform services than UI: authentication, observability, release tooling, API clients, design tokens, privacy controls, and SDK governance. Conversely, one cross-platform foundation can reduce duplicated fixes across several similar apps if a funded platform team owns it. Charge that team's cost to the decision. Without explicit capacity, shared infrastructure becomes an unowned dependency that every product waits on but no product can repair.
Include release topology in the estimate. Native apps can ship one platform while holding the other, which helps when store review or platform risk differs but can create feature parity confusion. A shared framework encourages one feature branch yet still produces two store artifacts with separate review, signing, crash behavior, and rollout. Define whether product promises simultaneous availability, equivalent capability, or platform-appropriate delivery, and measure coordination cost against that promise.
Also assess test architecture. Shared code can support shared unit and component suites, but device-level tests still need both platforms and representative OS versions. Native stacks may duplicate UI tests but often expose platform failures more directly. Price emulator and physical-device capacity, flaky-test ownership, accessibility automation, performance profiling, and release-candidate duration. Framework productivity measured before this pipeline exists is an incomplete comparison.
Record the decision as revisitable assumptions, not a permanent identity. Trigger review when product capabilities, platform strategy, framework support, staffing, dependency health, or release economics materially change; use evidence before considering migration.
Key takeaways
- All choices retain native release, device, permission, and incident responsibilities.
- Let product constraints eliminate options before comparing developer preference or nominal code reuse.
- Prototype a difficult end-to-end journey and include failure diagnosis and store packaging.
- Measure valuable shared behavior separately from platform workarounds and bridge code.
- Price dependencies, upgrades, staffing, testing, and escape hatches over several years.
FAQ
Which option is fastest to build?
It depends on the product slice and team. Cross-platform UI can reduce duplicate implementation for similar experiences, while direct native access can be faster for platform-heavy capabilities. Measure a representative slice with the people who will maintain it.
Is native always faster?
Native gives direct platform primitives, but customer performance depends on architecture, rendering work, I/O, images, startup initialization, and testing. Set journey budgets and profile the real implementation. Framework labels are not performance evidence.
Can native apps still share code?
Yes. Teams can share server contracts, generated clients, fixtures, design tokens, analytics schemas, and selected domain libraries. Decide whether the operational complexity of a shared binary module is justified by the stability and risk of the logic.
Conclusion
The best mobile framework is the one whose remaining platform-specific work, dependency risk, upgrade cadence, and staffing model the organization can own for the product's lifetime. A constraint map, high-risk prototype, and five-year ledger produce a better decision than a generic winner. Choose deliberately, then keep the escape paths visible.