Cross-Platform App Development, With the Trade-Offs Said Out Loud
React Native and Flutter are genuinely good answers for a lot of apps and genuinely wrong for some. Most agencies only tell you the first half. Here is the whole thing, including where we would talk you out of it.
- We build native too
- Framework chosen per project
- Recommendation on the first call

It Is Not a Discount. It Is a Different Set of Compromises.
The pitch for cross-platform is usually money: one team, one codebase, half the cost. That pitch is half true and it hides the part that actually matters, which is that you are choosing a different shape of risk rather than a cheaper version of the same thing.
What you are really buying is a single place to fix a bug and a single place to add a feature, for the entire life of the product. What you are really paying is a dependency on a framework you do not control, sitting between your code and two platforms that change every year.
For a large majority of apps — the ones made of screens, lists, forms, accounts and API calls — that is a good trade and we will say so. For the rest it is not, and we will say that too, even though it is the more expensive advice for you and the less profitable advice for us.
What You Genuinely Gain, and What You Genuinely Give Up
Not the marketing version of either column.
What you gain
Mostly After Launch, Not Before It
- A bug is diagnosed once and fixed once, instead of twice by two people who write it up differently
- Both platforms stay genuinely at feature parity, rather than Android quietly running three releases behind
- One team to hire, brief, onboard and keep, which matters more at four people than at forty
- One set of business logic, so the two apps cannot disagree about what a rule means
- Faster iteration once live, because shipping to both stores is one piece of work
What you give up
Small Things That Add Up
- Same-week access to new OS features. You wait for the framework, or you write the bridge.
- A layer between you and the bug. Some problems are in the framework, and then you are waiting on someone else.
- A few megabytes of app size you did not choose, which matters on a cheap phone with a full disk
- The last few percent of feel: scroll physics, keyboard behaviour, selection menus, transition timing
- A periodic upgrade tax. Nobody sits on a three-year-old framework version and stays shippable.
Notice that nothing in the right-hand column is about whether it works. It works. The column is about where the friction moves to.
The Saving Is Not in the Build. It Is in Year Two and Year Three.
People compare the two quotes and are disappointed that one codebase does not cost half. That is because the first invoice is the wrong place to look.
| Where the money goes | Two native codebases | One shared codebase |
|---|---|---|
| Design | Two platform treatments of the same flows | Still two treatments. No saving here at all. |
| Application code | Written twice, in two languages, by two people | Written once. This is the real reduction. |
| Platform-specific work | Included by definition | Still needed at the edges, and it is native code either way |
| Device testing | Both platforms, full matrix | Both platforms, full matrix. No saving. |
| Store submission and listings | Two of everything | Two of everything. No saving. |
| Fixing a bug after launch | Diagnosed and fixed twice | Once, and it ships to both stores together |
| Adding a feature after launch | Built twice, and the two drift apart | Once, and parity is automatic |
| Annual OS releases | Two sets of breaking changes to absorb | Framework upgrade, plus whatever it did not cover |
| Team you need in year three | iOS and Android capability, both kept warm | One capability, plus occasional native help |
An illustrative breakdown of where effort sits, not a quotation. The shape is consistent across projects; the amounts depend entirely on your app. Run your own figures through the app cost estimator or see pricing.
Year one looks close
Design, testing and store work are duplicated regardless, so the shared codebase saves on one line out of five. Expect a meaningful difference, not a halving, and be suspicious of anyone quoting a halving.
Years two and three decide it
By then you have shipped more code after launch than before it. Every fix and every feature happens once instead of twice, and that compounds quietly until it dwarfs the original difference.
Where the maths breaks
If the app accumulates a lot of platform-specific code, you are maintaining two codebases plus a framework. That is the worst of both, and it is why the limits below are worth reading before you commit.
Where Cross-Platform Actually Runs Out
Three places, consistently. If your app lives in one of them, the shared codebase stops paying for itself and starts costing you.
Heavy or gesture-driven animation
- Anything that has to track a finger at full frame rate on a cheap phone, not just on a flagship
- Complex shared-element transitions between screens, where a frame or two of lag is the whole effect
- Custom drawing that updates continuously: charts that follow a drag, canvases, games
- It can be done. It takes specialists, it takes longer than the native equivalent, and it is the first place a demo falls apart on real hardware.
Deep hardware access
- Custom camera capture with manual control, or processing frames as they arrive
- Bluetooth peripherals, especially pairing and reconnection behaviour that differs per platform
- Sustained background location, step counting, health and sensor streams
- On-device machine learning, audio processing, offline maps and anything else that is really native work wearing a wrapper
- Every one of these ends up as a native module, and enough of them means you have written two native apps with an extra layer on top
Platform-specific expectations
- Apps where iOS users expect an iOS app and Android users expect an Android one, not a compromise between them
- Deep system integration: widgets, Live Activities, quick settings tiles, share extensions, watch apps
- Products where a designer has opinions about scroll physics and keyboard behaviour, and is right to
- Anything sold on craft, where the last few percent of feel is the reason people pay
One useful test: write down the five hardest things the app has to do. If two or more of them are on this page, native is probably the honest recommendation, and we will give it.
The Escape Hatch, and the Point Where It Stops Being One
Neither framework traps you. Both let you drop into Swift or Kotlin and expose it to the shared code. Understanding what that costs is the difference between a sensible plan and a slow-motion mistake.
How it actually works
You write the platform code twice, once in Swift and once in Kotlin, and define a small interface that the shared codebase calls. The shared side sees one function; underneath, two implementations do the platform-specific work. For most needs somebody has already published a package that does exactly this, which is why the ecosystem matters as much as the framework.
Where it gets expensive is the boundary. Anything crossing it has to be serialised, so passing a lot of data frequently — camera frames, sensor streams, large lists updating continuously — is precisely the case the boundary is worst at. That is not a bug; it is the shape of the architecture, and designing around it is most of the skill.
What it costs you, honestly
- Two implementations to write, test and keep in step, plus the interface between them
- People on the team who can actually write Swift and Kotlin, which is the part that quietly disappears from other agencies
- A third-party package that is unmaintained becomes your code the day it breaks
- Debugging spans two worlds, so a crash can start in shared code and end in a native stack trace
- Past roughly a fifth of the app, the shared codebase is no longer the thing you are maintaining
We audit dependencies before committing to a stack, because the honest version of a cross-platform estimate depends on which packages you are trusting and how alive they are.
React Native or Flutter
Both are mature, both are backed by very large companies, and both are running apps on your phone right now. The choice is usually settled by hiring and by what you already have, not by a benchmark.
React Native
JavaScript and TypeScript, rendering real platform components. Best when you already have a React web team, want the largest possible hiring pool, or expect to share logic and types with an existing web product. Over-the-air updates for non-binary changes are a real operational advantage.
React Native developmentFlutter
Dart, drawing every pixel itself rather than using platform widgets. Best when you want the two platforms to look identical, when the design is heavily custom, or when animation-rich interfaces matter. The rendering model is its greatest strength and the source of its only real tell.
Flutter app development| Question you should actually ask | Points to React Native | Points to Flutter |
|---|---|---|
| Who maintains this in three years? | Any competent JavaScript or TypeScript developer can get productive | A smaller pool, but one that is usually mobile-native in mindset |
| Do you already have a web product? | Types, validation and business logic can genuinely be shared | No overlap. Dart is its own world. |
| How custom is the design? | Fine, with more effort once you leave platform components behind | Its natural home. Custom UI costs the same as standard UI. |
| Should it feel like each platform? | Closer by default, because it uses the real components | Achievable, but you are recreating the behaviour rather than inheriting it |
| Is app size tight? | Generally the lighter of the two | A larger floor, because it ships its own rendering engine |
| How much animation is there? | Good, with care around lists and gestures | Consistently strong, and the reason a lot of teams pick it |
A deliberately even summary. We do not have a house framework, and on some projects the correct recommendation is neither of them.
The Projects We Turn Away From Cross-Platform
These come up often enough to be worth stating plainly, before anyone has spent money.
Native iOS
Swift and SwiftUI, TestFlight, privacy manifests and App Review handled for you.
Learn moreNative Android
Kotlin and Compose, a real device matrix, and Play Console rollouts run properly.
Learn moreTesting & QA
Cross-platform does not reduce the device matrix by a single handset. Here is how we cover it.
Learn moreCross-Platform Questions We Get Asked
Will users be able to tell the app is cross-platform?
Not from a screenshot, and usually not from a minute of use. They notice it in the seams: a scroll that decelerates slightly differently from every other app on the phone, a keyboard that pushes the layout around in an unfamiliar way, a text selection menu that is not the one the system usually shows, a transition that is a frame or two late. Individually none of that is fatal. Together it produces the vague sense that an app is not quite from here, which people express as the app feeling cheap rather than as a specific complaint. The gap is much smaller than it was, and careful work closes most of it, but anyone who tells you it is zero has not looked hard enough.
How much does one codebase actually save over three years?
Less than half on the build and more than half on everything after it, which is the opposite of what most people assume. The initial build does not halve, because design still has to cover two platforms, testing still has to cover both sets of devices, and the store work is duplicated no matter what the code looks like. Where the saving compounds is maintenance: one bug fixed once, one feature built once, one library upgrade instead of two, one release process. Over three years, maintenance and iteration usually outweigh the original build, so the shared codebase wins on total cost even when the first invoice looks closer than expected. It stops being true if the app ends up with large amounts of platform-specific code, because then you are maintaining two codebases with extra steps.
Can we start cross-platform and move to native later?
Yes, and it is a legitimate plan, but be clear that it is a rebuild rather than a migration. Screens do not convert. What does carry over is everything that turned out to be the expensive part: the backend and its API, the data model, the design system, the analytics you have already defined, the store listings and the accounts. In practice a rewrite of a proven app is far cheaper and far faster than the original build was, because the arguments are settled and somebody can simply follow the app that already exists. The version of this that goes wrong is drifting into it: adding native code piece by piece until you are paying for both approaches and getting the benefits of neither.
What happens when Apple or Google ships a new platform feature?
You wait, or you write the bridge yourself. When a new capability arrives in an OS release, native apps can use it as soon as the tooling supports it. A cross-platform app can use it once either the framework exposes it or somebody writes a native module that reaches it. For a mainstream feature that is often weeks to months, and for something niche it may be never, in which case you are writing the native code on both sides yourself. This matters more than people expect for anything tied to the annual OS release cycle, and hardly at all for an app made of screens and API calls. It is one of the questions we ask at scoping, because the answer changes the recommendation.
Is a cross-platform app slower than a native one?
For what most apps do, no, and users cannot tell. Loading a screen, fetching data, rendering a list of a reasonable length and handling a form are all fast enough that the framework is not the bottleneck; the network usually is. The differences appear at the edges. Long lists with complex cells need more care than they would natively. Animation that has to track a finger at sixty frames a second on a cheap phone is harder. Startup time is generally a little longer because a runtime has to come up. Heavy on-device work, such as image or video processing, belongs in native code regardless of which framework wraps it. We set performance targets, including cold start and scrolling, as acceptance criteria rather than treating them as something to look at if anyone complains.
Which is the safer long-term bet, React Native or Flutter?
Both are backed by very large companies, both run apps you already use, and neither is going away on the timescale of your project. The honest risk is not abandonment, it is staffing and drift. React Native is JavaScript and TypeScript, so the hiring pool overlaps with every web team on earth and a web developer can be useful on it quickly. Flutter is Dart, which is a smaller pool but a more consistent one, and Flutter developers tend to be mobile people rather than web people who moved sideways. The second risk applies to both: upgrades. Neither framework lets you sit on an old version for three years, because the platforms move and the ecosystem follows. Budget for periodic upgrade work either way, and pick the one your future team can actually staff.
Get a Straight Recommendation, Not a Pitch
Describe the app and we will tell you whether cross-platform is right for it, which framework we would use, and when we would advise going native instead.