Built once for both.
Screens, state, navigation and the API client in a single project maintained by one team
one codebase both stores build from
Cross-platform app development · UAE
Cross-platform app development shares the screens, the state and the business logic. Identity, the two payment sheets and right to left sit at the seam, and they decide the framework.
Map what can be shared, before you pick a frameworkWhat cross-platform app development is
One codebase, one team and one release train cover most of an app. What the operating system owns is written per platform, whichever framework you choose.
Screens, state, navigation and the client that talks to your API. A Business Bay brokerage on a mainland DET licence, putting its own listings in front of clients, sits almost entirely in this column, which is what one codebase genuinely covers.
Anything the operating system owns: the identity app-switch, the two payment sheets, notifications, background limits. A Dubai Marina clinic group on a mainland licence, taking a verified sign-in and an instalment payment, holds most of its risk here.
Two review queues, two privacy declarations, two organisation accounts. One build feeds both, so the slower reviewer sets the launch date rather than the engineering.
The scope
What a cross-platform engagement actually produces. Three outputs from the shared layer, one at the seam, and two that only exist because there are two stores.
Screens, state, navigation and the API client in a single project maintained by one team
one codebase both stores build from
One team on one runway instead of two teams working to two calendars
what the shared layer saves is what funds the seam
The shared layer compiles to a native binary rather than running as a page inside a shell
a parity scorecard measured on handsets, not a claim
Camera, biometrics, the payment sheets and push reached through modules written once per platform
a module manifest agreed before the build
One release train feeding two review queues, with the slower one holding the date
both listings live on the same day
Flutter or React Native chosen after the module manifest exists, with the reasons and the cost of reversing it
a decision you can hold us to
Shared, or written twice
The per-platform column is the real build, whichever framework you choose. The shared column is the part one codebase genuinely covers.
| The seamWritten per platform | The shared layerWritten once, both platforms | |
|---|---|---|
| Screens and logic | Nothing, unless a screen has to follow a platform convention the other does not have. | One set of screens, one state layer, one client talking to your API. |
| Arabic and right to left | Android still needs the right-to-left flag in the manifest and a mirrored resource set of its own. | Shared, if the layout uses start and end from the first screen rather than left and right. |
| UAE PASS sign-in | The app-switch: a URI scheme registered in the iOS app, a queries entry in the Android manifest. | The authorisation flow and the session it returns to your app. |
| Apple Pay and Google Pay | Two payment sheets, two merchant identities and two sets of certificates, set up separately. | The cart, the order and the reconciliation code sitting behind both. |
| Document capture | The camera and photo permission strings, declared in the iOS binary and in the Android manifest. | The form, the validation rules and where the file is stored. |
| Push and background work | Two notification services, two entitlement sets and two sets of background-execution limits. | The message payload and what the app does when it opens. |
The per-platform column is the build, whichever framework sits above it, which is why the framework is chosen after this list exists rather than before it. A Dubai Silicon Oasis software firm on a free zone licence, with a live iOS product and nothing on Android, reads it for a different question: what the second platform inherits, not what the first is rewritten in. Still weighing whether this has to be an app? That question comes first, and the pillar sets out how the engagement runs.
What we find at the seam
Webzenia has worked with Gulf clients since 2018. These four decide where the shared layer stops, and none of them is settled by picking a framework.
Signing a user in through the national identity app means leaving your app and coming back. That is a scheme registered in the iOS binary and an entry in the Android manifest, not a line of shared code, and no framework removes it.
Apple Pay and Google Pay are set up separately, certified separately and settle separately. The choice reaches engineering as an integration and reaches the finance team as a second monthly reconciliation nobody asked them about.
A layout written with start and end mirrors on its own. One written with left and right does not, and the correction is every screen a second time, which is the retrofit that makes Arabic look expensive when it was only late.
Once the rails are written down the choice narrows on its own: what has a maintained binding, what your team can staff, and what the seam costs under each. Webzenia writes the list first and records the call with its reversal cost.
What the engagement covers
The engagement in six stages. The rail table comes first, because the framework choice is a consequence of it and not the other way round.
We settle what the app has to reach, then mark every rail shared or per platform. A JLT commodities trader under DMCC building a partner portal reaches almost nothing the operating system owns; a consumer checkout reaches most of it.
Flutter or React Native, chosen against the table and recorded with the reasons. The shared layer is then built once: screens, state, navigation and the API client.
The identity app-switch, the two payment sheets, notifications and background work, each written natively on both sides and bridged into the shared layer.
Tested in English and in Arabic on physical devices, because a mirrored layout and a longer string are the two things a simulator is most confident and most wrong about.
Two submissions, two privacy declarations and two review queues, planned as one release. The slower reviewer sets the date, so it is planned for rather than discovered.
The written record of what is shared, what is native and why, alongside the code. The next team inherits the boundary instead of rediscovering it one bug at a time.
Our stack
Two frameworks for the shared layer, two languages for the seam, one backend both platforms read. Select one to see when we argue against it.
Flutter draws its own widgets, so one visual result holds on both platforms, and its directional widgets mirror a layout right to left without a second stylesheet.
We build in Flutter when the interface has to be identical in both directions and on both platforms, and we use the directional insets from the first screen rather than converting a laid-out app later.
How the build runs
The framework is chosen in week two, not week zero. Everything before it establishes what the app has to reach, because that is the question the choice answers.
We list everything the app has to reach and mark each rail shared or per platform: the identity app-switch, the wallets and gateway, capture, notifications, background work. Then the framework is chosen against that table and written down with the reasons and what reversing it would cost. Nothing is built in this phase, which is deliberate.
The shared layer is built once: screens, state, navigation and the client that talks to your API, in both directions from the first screen. The seam is written natively on both sides and bridged, so a feature is never dropped to keep the codebase pure. Builds go to physical handsets, in English and in Arabic, while there is still time to change them.
One build produces two submissions, and the two do not clear at the same speed. We plan the release around the slower queue rather than the optimistic one, hold the faster listing until both are ready where a simultaneous launch matters, and keep the version numbers in step afterwards so a fix does not land on one platform and wait a fortnight on the other.
Reported against the rail table, so you can see what is shared and what is not.
Our commitment
A cross-platform build goes wrong at the seam. These four are written into the scope rather than agreed on a call.
Integrations listed before the framework.
You get the shared and per-platform split in writing before anyone commits to Flutter or React Native. A framework recommended before the integration list exists is a preference, and it is yours to audit.
The framework choice, recorded.
The decision is written down with the reasons and what changing it later would cost you. A five-year commitment made in week two should not arrive as a sentence in a proposal.
Native code where it matters.
Where the framework stops short we write a module in Swift and in Kotlin. No feature is quietly dropped, downgraded or replaced with a web view to keep the shared codebase tidy.
No hidden second codebase.
A per-platform branch inside shared code is a decision we write down and you approve, not one an engineer makes on a Thursday. Unrecorded forks are how one codebase becomes two without anyone deciding to.
Common questions
Keep exploring
Native Swift, and the entity Apple publishes as seller.
Kotlin, and the five manufacturer skins this market runs.
One widget tree, and an Arabic screen that mirrors itself.
For teams that already write React and have to hold it.
A shopping app costed against the repeat order.
Two-sided products where the fleet is the harder half.
Internal apps for staff who are not at a desk.
Subscription products whose customer is a licensed company.
A component system engineering builds from, in both directions.
Next step
Send the list of what the app has to connect to. We will come back with the shared and per-platform split, and the framework it points at.
Tell us what you need.