One codebase, two stores: what Flutter actually saves you
There is a moment in every mobile project where somebody says it out loud: so we need to build this twice? Two languages. Two teams. Two backlogs that start identical and drift apart by the third sprint. Two release calendars that never quite line up.
That moment is where a lot of good products quietly die. Not because the idea was wrong — because the idea cost double.
The tax nobody puts on the invoice
The obvious cost of building twice is engineering time. The expensive cost is everything downstream of it.
- Every feature is specified twice and understood slightly differently by each team.
- Every bug is found twice — and fixed once, in one platform, because the other team is mid-sprint.
- Every release is negotiated. iOS review takes a day, Play rollout is staged, and now your marketing date belongs to whichever platform is slowest.
- The apps stop being the same product. Six months in, iOS has a feature Android does not, and support has to ask which phone you are holding before answering a question.
That last one is the real killer, and it never appears in an estimate.
What one codebase changes
Flutter draws its own interface rather than wrapping the platform's. That sounds like a compromise until you notice what it buys: one description of every screen, one animation curve, one place where the button's disabled state is decided.
One codebase does not mean half the app. It means one definition of what the app is, rendered faithfully on both platforms.
Concretely, on the products I have shipped this way:
- One backlog. A feature is done when it is done — not done-on-iOS.
- One bug list. A fix lands everywhere in the same commit.
- One release. Version numbers actually match, which makes support tractable.
- Roughly the cost and calendar of a single app rather than two.
Where it stops feeling like a compromise
The old objection was that cross-platform apps feel cross-platform — the scroll is wrong, the back gesture is wrong, the keyboard covers the field. That objection was fair, and it is mostly historical now, but only if somebody does the work:
- Respect each platform's conventions. iOS gets iOS navigation and iOS-style modals. Android gets Material and a real back gesture. Same codebase, different manners.
- Use the platform when it matters. Native code for the camera pipeline, for background location, for anything the OS does better. Flutter is not a wall; it is a default.
- Test on the phone people own, not the one on your desk. A three-year-old mid-range Android is the real target.
Three times I would tell you not to use it
I would rather lose the work than build the wrong thing, so:
- Your app is a platform feature. A widget-first app, a Watch app, something living in Shortcuts or App Intents — build it native. You would spend the savings on bridges.
- Sustained heavy graphics or real-time audio. If your product is a video editor or a synth, go where the platform APIs are first-class.
- You already have a strong native team. The best technology is the one your people can maintain after I leave.
The honest summary
Flutter is not magic and it is not a shortcut. It is a way of paying for your app once instead of twice, and spending the difference on the parts users actually notice — polish, speed, and the third feature you thought you could not afford.
It is what I have shipped most, and what my own apps run on. Which also means I know exactly where the edges are.