Native or cross-platform for your mobile app? The criteria, and what each one really costs
For most business apps the right answer is a single codebase: writing both platforms once in React Native or Flutter costs less and ships sooner than splitting your team in two. Native development earns its keep when specific triggers are present, such as deep hardware access, continuous background work, a tight performance budget on older devices, or a need to adopt new OS features the day they ship. There is a third path in between, sharing business logic through Kotlin Multiplatform while keeping the interface native on each platform. Before any of that, though, comes question zero: do you actually need an app in the stores, or would the web do?
Question zero: does the app need to exist
Apple's review guidelines are blunt here. Guideline 4.2 requires that your app "include features, content, and UI that elevate it beyond a repackaged website," and 4.2.2 excludes apps that are primarily marketing material, web clippings, content aggregators or a collection of links. If the plan was to wrap your site in a WebView and submit it, the plan ends here.
The app itself is not free either. It means two store accounts, two review queues, two release channels, signing certificates to keep alive, and zero control over when a user actually receives an update. On the web you fix a bug in the afternoon and ship it the same day. In the stores the same fix goes through review, and some share of your users sits on the old build for weeks.
So when is a PWA enough? On Android it holds up well: install prompts, web push and offline caching all work. On iOS the limits are still real. Installation happens only through Safari's Share menu and "Add to Home Screen," there is no automatic install prompt (beforeinstallprompt is not supported), web push works only for home screen web apps rather than a site open in a tab, and there is no background sync. In other words, a user has to complete a three step install on their own before you can send them a single notification. For internal tools, field team apps and B2B situations where you can tell someone "follow these steps," a PWA is a perfectly sensible choice. For a consumer product it narrows your reach on iOS considerably.
Cross-platform is no longer a fringe bet
Appfigures data puts apps published with React Native or Flutter at 7% of all new app releases in 2020, 12% in 2021 and 15% in 2024. The engineering side settled down over the same period. React Native's New Architecture became the default in version 0.76 (October 2024), which removed the bridge between JavaScript and the native side and made synchronous calls and modern React features like Suspense workable. On the Flutter side, Impeller is now the default renderer, and the 2026 roadmap targets removing the legacy Skia backend on Android 10 and above and making WebAssembly the default for web builds.
Shopify's five years: the measured result and the bill
When Shopify published its React Native decision in January 2020, it came with numbers. The Arrive app (now Shop) shared 95% of its code between iOS and Android, the internal Compass app reached 99%, and Compass shipped on both platforms within three months. The team had estimated 80% going in. The Arrive team reported being twice as productive as with native development, and that was the comparison against building for one platform. The rewritten app also crashed less on iOS than the native version it replaced.
Five years on, the numbers held: over 99.9% crash free sessions, and sub-500ms P75 screen loads in the Shopify app. The same retrospective lists what they paid for it. Debugging in React Native is flaky. Moving to each new React Native version takes a significant amount of work. Reliance on third party libraries grows and has to be maintained. And they still need native specialists for hardware integration, background tasks and platform updates.
The exception inside their own decision is worth copying. They kept the retail point of sale app native on iOS, and the thresholds they gave are directly reusable: React Native is not their default when the app has to run on older hardware below 1.5 GHz, do heavy processing, run many background threads, or hit the top end of performance.
Why Airbnb walked away, and what that story is worth
Airbnb shut down its React Native effort in 2018 after two years of investment. The write up is worth reading because most of the reasons are not technical. Much of the pain came from the hybrid model: native and React Native screens living side by side in one app, bridging between them, sharing navigation state, and working across two separate debugging worlds. Even so, the internal survey was striking. 63% of their engineers would have chosen React Native again given the chance, and 74% would consider it for a new project. What decided the outcome was organisational, including how much harder it had become to hire strong native engineers.
That story is eight years old now, and both React Native and Airbnb have changed since. The lesson is not "React Native is bad." The lesson is that a mixed architecture costs more than either architecture on its own. When you decide, price "let's leave half the screens on the other stack for now" as the most expensive option on the table.
The line missing from the quote: the upgrade tax
This is the item cross-platform proposals leave out most often. React Native supports only the latest three minor series at any time, and a new series ships roughly every two months. In practice that means the version you build on today drops off the supported list in about six months. As of August 2026 the current stable is 0.87; 0.87, 0.86 and 0.85 are supported, and 0.84 and earlier are not.
Deferring the upgrade does not make it cheaper. Each deferral stacks a bigger jump plus a bulk update of every library pinned to it. Put two upgrade windows a year into the calendar and the budget from the start. A proposal without that line looks cheaper and closes the gap in year two. Flutter works the same way, with a slightly less churn prone library ecosystem.
The third path: share the logic, keep the UI native
Kotlin Multiplatform does exactly this. Networking, caching, business rules, validation and state handling live in shared Kotlin, while the interface is written separately in SwiftUI on iOS and Compose on Android. Google announced official support for the model at I/O 2024 and stated the reasoning plainly: the part most worth sharing is the business logic, the part that is agnostic to the user interface. They run it internally too, with Google Docs sharing business logic across Android, iOS and web.
For teams that want to share the interface as well, Compose Multiplatform for iOS was declared stable in May 2025. JetBrains puts the cost at roughly 9 MB added to iOS app size versus a native SwiftUI equivalent, with accessibility support including VoiceOver and Full Keyboard Access.
Who is this path for? Teams that already have two working native teams, products where the interface genuinely needs to feel native on each platform, and codebases where the thing being written twice is complicated domain logic. The share percentage stays lower by design. But the part you do share is the part that produces the most drift and the most bugs when two teams write it independently.
When native is still the answer
Write native if more than one of these applies: deep work with the camera, Bluetooth, NFC or custom hardware; continuous background location or syncing; experiences where input latency is the product itself (maps, drawing, camera, games); a real performance budget on older devices; a need to use new iOS or Android capabilities the day they are announced.
There is also the case people skip past quietly. If the app will only ever live on one platform, cross-platform buys you nothing. You just add a toolchain and an extra upgrade calendar.
Who is on the team matters more than the framework
This should decide it more often than it does. If you have a strong React team on the web, React Native will get you further faster than an alternative that looks better on paper. If the team knows Kotlin and Swift, learning Dart for Flutter is not a three week delay, it is a productivity dip spread across months.
Whatever you pick, at least one person needs to be able to open Xcode and read a native stack trace. A cross-platform project where nobody understands the native side stops at the first signing error, the first store rejection or the first native module conflict. That gap can be covered from outside, but it is expensive when it was never planned for.
Mobile security does not work like server security
Two points hold regardless of framework. First, the app bundle sits on the user's device and can be inspected. A JavaScript bundle in React Native, compiled code in Flutter, a binary in native, all of it is open to reverse engineering. An API key embedded in the app is not a secret. The distinction in the hardcoded secrets post applies directly: a key on the client is a published key. Access to third party services should go through your own backend.
Second, dependencies. Cross-platform projects typically arrive with a longer list of third party packages, and that list lands straight on user devices. The controls in software supply chain security apply to mobile without modification. If you use over the air updates, treat that channel carefully: it is a path that delivers code to user devices while bypassing store review, so it needs to be signed and tightly restricted.
Four things to do this week
One: look at your real device mix. What is the iOS to Android split in your analytics, and how old are the devices? If one platform dominates, half the argument is already settled.
Two: write down the five things the app genuinely has to do, and note the device capability each one requires. If camera stream processing, background sync and Bluetooth pairing are not on that list, the cross-platform route is open.
Three: budget the maintenance up front. Two framework upgrades a year, two OS major versions to keep up with, store policy changes. Cut that line from the quote and it comes back at the end of year one.
Four: pick one small but real screen and get it working on both platforms within two weeks. This decision is made by the velocity your team shows in those two weeks, not by a comparison table. If you want the delivery side in place first, CI/CD for small teams is a reasonable starting point for a mobile pipeline, and for the commercial side of the call see off the shelf versus custom software and measuring and prioritising technical debt.
Need help with this topic?