Native where it earns its cost.

Most of the mobile work we ship is cross-platform, and we would rather you read that here than discover it later. It is also the reason this page is worth your time: we spend our days deciding where the shared layer stops paying, which is a more useful thing to buy than a claim of native purity.

Because the real question is never “native or cross-platform” in the abstract. It is which part of this product is paying the cross-platform tax, and what taking just that part native would cost. The answer is almost always one screen, one module or one surface — and often it is nothing at all.

FACT 01

We built and shipped the native iOS and Android builds of a social-media scheduler, live in both public stores. It is published under the client’s own developer account, where it continues to be updated — we do not publish or operate it, and this page does not claim every release since.

FACT 02

The Apple Watch surface of that same product is ours and it is native — a separate target with its own lifecycle, which is precisely the kind of surface a shared framework reaches badly and precisely the boundary this page is about.

FACT 03

Two other mobile products named further down are Flutter, and the page says so on each: the Ministry of Foreign Affairs app for Qatar, and the AI networking app. There are no app-store ratings, download numbers or crash-free percentages anywhere here. None has been cleared.

Move the boundary yourself ↓

30 minutes, working session. Tell us which part of the app misbehaves and on what hardware. You leave with a one-page read either way, the answer is frequently “change nothing”, and nobody follows up more than once.

A mobile contacts screen from the AI networking app: people captured at an event shown as clustered pins on a dark map, with a four-item tab bar across the bottom for profile, camera, contacts and settings
A cross-platform app with a native-shaped problemThis one is Flutter, and it is here on purpose. Capture on whatever phone the visitor owns, in a room with no network — device variability and a dead connection are the two conditions that push work down to the native layer more often than anything else. AI networking app case →

Six times the shared layer stops paying, and what it actually costs

Left of the line is what you are living with. Right of it is the price of crossing — because crossing is never free, and a page that pretends otherwise is selling you a second codebase.

THE SITUATION

One screen is dragging the whole app down

Everything else is fine. One surface — sustained camera, a heavy list, a real-time visualisation — misses the frame budget on mid-range devices, and somebody in the room has started saying the words “full rewrite”.

WHAT CROSSING COSTS

One screen, twice: two implementations, two sets of tests and two places a designer’s change lands. That is a real cost and it is a fraction of a rewrite. Rewriting an entire app because one view drops frames is the most expensive available way to solve that problem.

THE SITUATION

The capability has no maintained bridge

A hardware integration, a platform API or an SDK that ships native only. The plugin you found is abandoned, half-implemented, or wraps a version two majors behind the one you need.

WHAT CROSSING COSTS

A module you now own on both platforms, over platform channels or JNI, plus the obligation to keep up with each SDK. Owning it is usually still cheaper than depending on a stranger’s repository that stops being updated the week you ship.

THE SITUATION

The background behaviour is a mystery

It works on iOS and not on Android, or on one manufacturer and not another. The bug report says “it stops overnight”, and nobody has read what either OS actually guarantees about the work you are asking it to do.

WHAT CROSSING COSTS

Frequently nothing: this one is usually a reading problem rather than a native one. Doze buckets, standby, foreground-service obligations and OEM battery managers are documented, and the fix is often a scheduling change inside the app you already have.

THE SITUATION

The watch, the widget, the extension

Product wants a surface the shared framework reaches badly or not at all, and every estimate comes back as “that is a separate app” — which is nearly true, and is why it keeps being deferred.

WHAT CROSSING COSTS

It genuinely is a separate target with its own lifecycle, its own state and its own review. The saving is that it does not need the whole app behind it — it needs a small, well-drawn contract with the data the main app already has.

THE SITUATION

Two native teams, one product, two behaviours

iOS and Android drifted. The same feature works differently on each, nobody planned it, and QA finds it after release — which is the expensive moment to find it.

WHAT CROSSING COSTS

This one crosses the other way. The fix is usually to move rules back to a shared layer — validation, pricing, permissions — and leave the platforms only the presentation. We will say so, even though it is the smaller engagement.

THE SITUATION

The native code has exactly one owner

Written by somebody who has left, in a style nobody else uses, with no tests — and it is the module everything else depends on. Nobody will touch it, so the roadmap quietly routes around it.

WHAT CROSSING COSTS

A written read first, then tests that pin the current behaviour, then change. The order matters: a rewrite that starts before the behaviour is pinned re-creates the same module with a different single owner.

Move the line and watch what you buy

A schematic app as a stack of six surfaces, ordered by how hard each one pushes towards native. Drag the boundary down and surfaces cross it one at a time. Each position tells you what you gain, what you now maintain twice, and how many codebases you are running. Both ends of the slider say something honest.

SCHEMATIC — A TYPICAL PRODUCT, NOT YOURS ALL SHARED
0 native
What you maintain
1 codebase

ONE RELEASE TRAIN · ONE TEST SUITE

What that buys, and what it costs

Everything is built once. Two surfaces are underserved at this position — the watch and widget targets the framework reaches badly, and sustained camera work that misses the frame budget on mid-range devices. For most products this is still the right trade, and we will tell you when yours is one of them.

The instrument is schematic, and the ordering is the part worth arguing about on a real product. Your camera screen may be trivial and your background sync may be the thing on fire. Working out the true order for one specific app is the first engagement below, and it takes a week or two.

The scheduling calendar in a browser: a week view with scheduled posts stacked on one afternoon, a campaign filter, and controls for creating a single post or a whole campaign
One set of rules, five surfacesThis is the web surface. The same scheduling rules also have to hold on native iOS, native Android, a watch app, an AR view and a voice assistant — and if they fork, the product has five different answers to “when does this post go out”. Deciding the shared/native line deliberately is how that is avoided; discovering it late is how it happens. Read the case →

What each side of the line actually asks of you

Two precise things per platform rather than a wall of vocabulary. If your engineer is reading this, these are the four they will check us on.

IOS — CONCURRENCY AND THE MAIN ACTOR

Swift concurrency with async/await and actors, structured rather than a completion handler wearing a new coat, and a clear account of what must not run on the main actor. We migrate a codebase deliberately or leave it alone: half-migrated concurrency is worse than either end state, and it is not something to do in passing while fixing a bug.

IOS — TARGETS THAT ARE NOT THE APP

App extensions, WidgetKit and watchOS are separate targets with their own lifecycles, their own memory limits and their own review. SwiftUI alongside UIKit in one app rather than instead of it, because a full migration is a project and rarely the one you meant to fund.

ANDROID — COROUTINES AND FLOW

Kotlin coroutines with structured concurrency, Flow for streams that outlive a screen, and Compose alongside the view system where that is what the codebase is. Cancellation handled properly, because a coroutine that survives its screen is the leak nobody attributes correctly.

ANDROID — WHETHER YOUR WORK RUNS AT ALL

WorkManager, doze and app-standby buckets, foreground services and the notification obligation that comes with them. Fragmentation today is mostly OEM battery managers rather than API levels, so the question is not “which Android version” but “which manufacturer, and what do they kill”.

Across the line, whichever side it falls on: deep and universal links, biometric authentication, keychain against keystore, and code signing and provisioning as a recurring tax rather than a one-time setup. A rejection costs about a week no matter how small the cause was, which is why store requirements belong in the plan rather than in the release week.

What is native here, and what is not

The left column says which side of the line each piece of work sits on, because on a page like this that label is the whole claim.

Native · iOS & Android
Social media scheduler
Both public stores

A named native product you can check before you finish this sentence

We built and shipped the native iOS and Android builds of a social-media scheduler, live in both public stores. The hard part is what the product does: scheduling and publishing. Background execution, notification delivery and token refresh behave differently on each platform, change with each OS release, and have to keep working through a September the app was never tested against. It is published under the client’s own developer account and is still updated there; we do not publish or operate it, and we are not claiming every release. Read the case →

Native · watchOS
Apple Watch surface
Its own target

The surface a shared framework reaches worst

The Watch surface of the same product is ours and native. A watch app is not a small phone screen: it is a separate target with its own lifecycle, its own memory ceiling, its own complications and its own review, and the useful design question is what tiny contract it needs from the phone rather than how much of the app to squeeze onto it. This is the clearest example on the page of a surface where crossing the line is the right call rather than an indulgence. Read the case →

Five surfaces
One set of rules
Mass adoption

Where the boundary question came from

The platform was architected and led from zero to mass adoption across web, native mobile, AR, Alexa and Apple Watch — one architecture, many surfaces. The hard part is surface sprawl: five interaction models that must not fork the business rules underneath them. Every argument on this page about deciding the shared/native line deliberately rather than discovering it comes from having had to hold five of them at once. Read the case →

Flutter
Qatar MoFA
Both public stores

Cross-platform, and it belongs here for a different reason

The official app of the Ministry of Foreign Affairs of Qatar, shipped on iOS and Android, in English and Arabic with full right-to-left layout. This build is Flutter, and saying so is the point: it is here as evidence of passing store review and a government body’s own review, not as native work. If we let it sit in this section unlabelled, everything else on the page would be worth less. Qatar MoFA case →

Flutter
AI networking app
Offline & on-device

The conditions that push work down to the native layer

Face-recognition capture, offline mode and CRM export, in an app that has to behave in a real room on whatever phone the user brought. Also Flutter. It is on this page because its two hardest conditions — device variability and the network being gone — are the ones that most often decide that a particular module belongs on the native side, whatever the rest of the app is written in. AI networking app case →

Read the left column again and you have the honest shape of this page: one native product with a second native surface, and a great deal of cross-platform work around it. We are not going to pluralise that into a native portfolio, because you would find out.

Bring the screen that misbehaves and the device it misbehaves on. Those two answer most of a Data Flow Audit on this subject.

Two of the Fluvius team standing on a rock outcrop in Central Park with the Manhattan skyline behind them, one in a Fluvius t-shirt
New York · between working sessions

Mobile arguments get settled in the same room

The boundary conversation goes badly over a ticket and well across a table, because it is half technical and half about what the team can carry for three years. We go where the product is, sit with the people who will maintain it, and write the line down while everyone is still in the room. About Fluvius →

A product slide about scheduling across many channels on the screen behind a working session them, one holding a small dog

Working inside a native team that already exists

Most of this work happens in somebody else’s repository, next to engineers who will still be there long after we are not. That shapes how we behave more than any technical preference does.

YOUR CONVENTIONS

Your architecture, your naming, your review path. We do not arrive and introduce a pattern your team has not agreed to, and we do not start an architectural argument in a pull request. If we think a convention is costing you, that is a conversation with the lead, not a unilateral commit.

YOUR ACCOUNTS

Apps, certificates, provisioning profiles and store listings belong in your developer accounts from day one, and we prefer it that way. It is also the arrangement that makes the end of an engagement uneventful, which is the point.

THE HANDOVER IS THE DELIVERABLE

Tests around the behaviour that has to stay identical, documentation written for whoever inherits the module, and a native engineer on your side who can change it on a Thursday without calling us. If we are the only ones who can maintain what we wrote, the job was done badly.

DIRECT ACCESS

You talk to the engineers doing the work — no account-manager layer — and a senior architect signs off every project. On a two-platform engagement that matters more than usual, because most drift between iOS and Android starts as a small decision nobody wrote down.

The work, as we actually sell it

Timelines are indicative ranges and depend on scope. Several of these are deliberately narrow — that is the argument of the page rather than a gap in it.

The boundary read

1–2 WEEKS

Which parts of your app are paying the cross-platform tax, on which devices, and what taking each one native would cost in build and in maintenance. Frequently the recommendation is to change nothing. You keep the document either way, and this is the engagement we would rather you bought first.

One screen taken native

4–10 WEEKS

The surface with a sustained hardware or performance requirement, rebuilt native and embedded in the app you already have — so it ships as a change rather than as a rewrite, and the rest of the product carries on being one codebase.

Native modules behind your app

3–8 WEEKS

Written and maintained by us over platform channels or JNI, with a typed contract at the boundary, rather than depending on a repository that a stranger stopped updating. You get the module and the tests that pin what it does.

Background and lifecycle work

3–8 WEEKS

Making background behaviour do the same thing on both platforms, within what each OS actually guarantees rather than what the documentation implies — including the manufacturers that quietly do something else.

Watch, widget and extension surfaces

4–10 WEEKS

The targets a shared framework reaches badly, built against their own lifecycles with a small contract back to the main app. We have shipped a native Watch surface on a product that also had four others.

Taking over native code with one owner

4–10 WEEKS

A written read first, then tests around the current behaviour, then change — in that order, so the module ends up owned by your team rather than by a different single person.

The honest alternative

SAME CALL

If the answer is that your product should stay cross-platform — or become it — we say so and point you at Flutter & React Native rather than sell you two codebases. That page is where most readers of this one should end up.

A second pair of eyes before you commit

1 WEEK

You have already decided to go native and want somebody to try to talk you out of it, in writing, with the counter-argument spelled out. Cheap, fast, and occasionally it saves a year.

What you are probably thinking

“Isn’t most of your mobile work cross-platform?”
Yes, and we say so at the top of this page rather than let you discover it. That is precisely why this page is about the boundary: we spend our time deciding where the shared layer stops paying, which is a more useful thing to buy than a claim of native purity. The native product we do name is linked in both stores, so it takes ten seconds to check.
“Do we need to go native?”
Usually not, and the read that says so is the cheapest engagement here. When you do, it is almost always one screen, one module or one surface rather than the whole app. Rewriting an app because one view drops frames is the most expensive way to solve that problem, and it is the one most often proposed.
“Can you work inside our native team’s codebase?”
Yes — your conventions, your review path, your architecture. We do not arrive and introduce a pattern your team has not agreed to. A codebase nobody in-house can read is a liability rather than an upgrade, and that is as true of a pattern we like as of one we inherited.
“Swift concurrency or the old thing?”
Whichever your codebase already uses, migrated deliberately rather than opportunistically. Half-migrated concurrency is worse than either end state. A migration is a decision with a schedule attached, not something done in passing while fixing an unrelated bug.
“Can you publish under our developer accounts?”
Yes, and we prefer it. Apps, certificates and store listings belong in your accounts from day one. The product we name on this page is published that way, under the client’s own account, which is exactly the arrangement we would set up for you.
“We tried an outside dev shop and it went badly.”
Usually the same three causes: no senior person accountable, a demo-grade codebase, and a handover that never happened. Here you get direct access to the engineers doing the work, no account-manager layer, a senior architect signing off every project, and tests and documentation as part of the deliverable.

The record behind the pages

200+
Clients served
10+
Years of happy clients
7+
Years our longest clients have stayed

Many of our clients have been with us for 7+ years straight. The Upwork and Clutch records are independently verifiable.

Looking for this as an engagement rather than a stack? Mobile App Development sells the team and the outcome. If your product is better served by one codebase, the honest neighbour is Flutter & React Native. See also all our work.

Not ready to talk? Take the checklist.

One page: 12 questions to answer before you rewrite an app natively. Which surface actually misses the frame budget, and on which devices. Whether the capability you need has a maintained bridge or an abandoned one. What each OS genuinely guarantees about your background work. Who owns the native modules you already have. What two codebases cost per year in review, release and drift. And which single screen would fix eighty per cent of the complaint.

Tell us which part of the app is misbehaving, and on what.

A free 30-minute working session, not a sales call and not a demo — there is nothing to demonstrate. Bring the screen people complain about, the devices it happens on, and the capability you cannot reach from where you are.

You keep a one-page read either way: the specific surface we would take native if any, roughly what it would cost to build and to keep, and the cheaper thing we would try first. The answer is frequently “change nothing”, and nobody follows up more than once.

RELATED SERVICES → Mobile App Development All services