essay

Two mobile codebases to maintain one product is usually one codebase too many.

2026-04-21 · 5 min read · chris olson

Every small team I have seen ship a native iOS app and a native Android app has regretted the second one. Not because writing native is hard — it is not — but because the work of keeping two codebases at feature parity consumes most of the engineering budget the product cannot spare. Flutter is the compromise I keep recommending. This post is about why.

01 the cost math

A two-engineer team can carry one mobile codebase. A two-engineer team cannot carry two mobile codebases without sacrificing something — usually the Android side, usually the quality of the release cadence, usually both. The hidden cost is not the initial build; it is every feature you ever want to ship after launch.

Flutter collapses that math. One team, one codebase, one release pipeline. The trade is a platform layer between you and the OS — you give up some access to the most niche native behaviors — but for ninety percent of product surface area, it is a trade that pays for itself in the first year.

I have shipped three Flutter apps in the last two years — hewn, fisherman's log, bitewise — and none of them could have justified the cost of two native codebases.

02 where native still wins

Flutter is the wrong choice when the app is the platform integration. A Siri-first shortcut app, an Apple Watch companion that has to behave exactly like system apps, a game that needs Metal at sixty frames per second without a single dropped frame — these are cases where you should go native and pay the cost.

It is also the wrong choice when your app is ninety percent WebView already. If you are wrapping a web experience, just ship a PWA or a thin native wrapper around a web app. Flutter is a commitment to a rendering engine; do not take it on for screens that are just a view into your API.

03 state management and offline-first

The Flutter landscape around state management has settled. I use Riverpod for almost everything — it is the closest thing to the Redux/Zustand mental model I had on the web, with the ergonomics of code-generated providers. Drift is the SQLite layer I reach for; it gives me type-safe queries and migrations without handwriting the wire format.

The offline-first story is the real reason I keep picking Flutter for product work. My users are not always on wifi. They are on boats. They are in shops with concrete walls. They are in basement rooms with bad cell reception. If the first tap on the app shows a spinner because the network is not there, the app is broken — regardless of whether the API is "up."

fisherman's log works entirely offline by design. Every catch lands in a local Drift database; the environmental snapshot pulls from a queue that WorkManager drains when connectivity returns. The user never waits on a round trip to log a fish. That behavior is not a feature I tacked on; it is the first decision of the schema.

04 the design system question

The thing Flutter developers argue about most is whether to lean into Material, lean into Cupertino, or build a custom design system. For product work I almost always build custom. The two platforms' native-looking widgets are close enough that the uncanny-valley effect is loud when you mix them badly.

A custom design system in Flutter is cheaper than you expect. The widget tree is the component model; there is no CSS/JSX impedance mismatch to fight. In hewn the entire visual language is three scaled spacing tokens, two type ramps, and one oxblood accent. The whole design system fits in a single file.

05 the apps I shipped to prove it

A quick shape of the three projects that back this argument:

  • hewn — Flutter + Supabase + flutter_scene. A staged GLB viewer lets the user scrub through project progress in 3D. Offline-first Drift cache; Supabase sync when network returns.
  • fisherman's log — Flutter + Drift + WorkManager. Environmental snapshots fetched lazily and queued; an on-device pattern engine scores lures against historical conditions.
  • bitewise — Flutter + Firebase AI + Gemini 2.5 Pro. A photo identifies the lure; the same app surfaces nearby water bodies from USGS and Recreation.gov.

Three different problem domains, one codebase per app, zero parity meetings. That is the argument for Flutter in a sentence.

06 when I still reach for native

Never for product work. For a platform-specific tool — an extension, a companion, a deeply OS-integrated utility — yes. Those are not the apps a small team is trying to ship anyway.

If the product is an app for humans to use, Flutter is usually the right answer. The cost math does not care about your language preferences.