Cross-platform mobile: the gap between the pitch and the build
August 5, 2025
Flutter and React Native can both ship great apps. The question is where your team will hit the native boundary and how painful that will be.
The pitch is true, up to a point
Write once, run on iOS and Android. In the common case — standard UI components, network calls, local storage, navigation — both Flutter and React Native make good on this promise. The productivity gain over two native codebases is real.
The pitch stops being true at the native boundary. Camera access with custom processing, Bluetooth peripherals, background audio, deep OS integrations, NFC — these require native code. And native code in a cross-platform project means two implementations, two maintenance surfaces, and a third layer of glue between them.
The question to answer before choosing a framework isn't "can this framework do what I need?" It's "how much of what I need lives past the native boundary?"
Flutter: rendering ownership as a feature
Flutter doesn't use platform UI components. It draws everything itself with Skia (or Impeller on newer versions). This is either a strength or a weakness depending on what you're building.
Strength: your app looks identical on iOS and Android. Custom animations, complex layouts, branded UI — they're consistent without platform-specific tweaks.
Weakness: you don't get platform-native behavior for free. A date picker that feels natural on iOS and a date picker that feels natural on Android are different components. Building both, or accepting that one platform's users get a slightly foreign experience, is a real choice.
Dart is also a real cost if your team doesn't know it. The language is pleasant, the tooling is solid, but "we can just pick it up" consistently underestimates the time it takes to write idiomatic Dart at speed.
React Native: the ecosystem trade-off
React Native renders platform-native components. A <Text> on iOS is a UILabel. On Android, it's a TextView. This buys you platform-appropriate defaults and makes accessibility correct without extra work.
The trade-off appears at the JavaScript bridge (or JSI in newer RN) — the point where JS and native code talk to each other. For most interactions it's invisible. For high-frequency updates, gesture handling, and camera pipelines it can be a bottleneck.
The ecosystem advantage is substantial. If your team has React expertise, the mental model transfers directly. The component library ecosystem is deep. Shared business logic with a web app is genuinely achievable, not just theoretically possible.
Where both frameworks fail the same way
The failure modes that aren't framework-specific:
Offline states are an afterthought. Both frameworks make the happy path easy. Flaky connectivity, partial syncs, and offline-first behavior require deliberate design. This almost always lands late and costs more than planned.
"Shared code" becomes tangled coupling. The promise of sharing business logic with a web app is real, but it requires explicit boundaries — shared types, pure functions, platform-agnostic abstractions. Without those boundaries, the shared layer slowly accumulates platform assumptions and becomes a liability.
List performance degrades invisibly. A FlatList or a ListView with 500 items and complex row components will perform badly if you don't actively measure it during development. By the time users complain, the fix requires refactoring row components you thought were done.
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Row item={item} />}
getItemLayout={(_, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index })}
removeClippedSubviews
/>
getItemLayout is the most underused optimization in React Native — it lets the list skip measurement for fixed-height rows and scrolls to arbitrary positions instantly.
The release process deserves a service
Signing, provisioning profiles, store metadata, phased rollouts — this machinery is a real project. Treat it as one. Fastlane, EAS Build, or Codemagic all reduce the manual surface area significantly. The teams that struggle here are the ones that automate the CI build but leave release automation to a senior engineer's tribal knowledge.
References
Hi, I'm Martin Duchev. You can find more about my projects on my GitHub.