Guide · every mechanism sentence quoted from docs.modaal.dev, 10 September 2026

    How to build a cross-platform mobile app with AI in 2026

    Building for iPhone and Android at the same time used to mean one of two compromises: write everything twice, or write it once in a framework that makes both apps the same. AI agents changed the arithmetic. This guide walks the route where each feature’s logic is written once, each platform gets its own native screen, and the build — not a tester — proves the two apps behave the same. It also describes the React Native route fairly, because for some apps it is the better one.

    Verified

    The short answer. Decide what “cross-platform” you want before the first prompt — shared screens, or shared logic with native screens. Write the first feature as scenarios, not as a wish. Pick the template that matches your Android plans, because that decision holds for the life of the project. Let the agent write a plan you approve, then the logic once and the two screens, and let the build replay every recorded scenario on both platforms. Publish to both stores with the accounts in your own name. The only thing no tool does for you is the first step.

    The rest of this page is those steps in order, with the parts that are specific to Modaal marked and the parts that generalize to any builder marked too.

    Step 0: decide which cross-platform you mean

    There are two honest ways to get one app onto two stores from one project, and they make opposite trades.

    Share the screens. One codebase — JavaScript with React Native, or Dart with Flutter — draws the interface once and renders it on both phones. The two apps are identical because they are the same code. This is the route most AI builders take, and if your team writes React and your product is forms, lists and feeds, it is fast and well-trodden. Its cost is structural: the screens follow one set of conventions on both platforms, and the platform reaches your code through a bridge.

    Share the logic, not the screens. Each feature’s behaviour — its state, the actions that change it, the effects it requests — is written once. Each platform gets its own interface in its own toolkit: SwiftUI on iPhone, Jetpack Compose on Android. The apps look and feel native on each phone because they are native on each phone; what they share is what they do. Its cost is also structural: “you write the views twice and the behavior once.”

    The builder comparison sorts ten tools by which of these they ship; React Native vs native argues the decision. This page assumes you chose the second route, and comes back to the first at the end. One thing is true of both: say it in the first prompt. Bolt’s own Expo guide puts it plainly — “Projects created for web do not easily switch over to mobile” — and the same is true of a project started for one platform on any tool.

    What you need before anything runs on two phones

    A Mac with Xcode. iPhone apps are compiled, signed and uploaded by Xcode, which only runs on macOS. Modaal runs on your Mac and builds through Xcode, so “you still need a Mac.” Cloud builders on the shared-screens route compile on their own machines instead.

    The Android SDK. For the two-platform template Modaal checks for it and shows an “Action needed: install the Android SDK” banner with the steps if it is missing. The Android app is an ordinary Android Studio project, so Android Studio is what you open it with.

    Two store accounts, both in your name. Apple Developer Program at “99 USD per membership year”, Google Play at a one-time 25 USD registration. You don’t need either until the app leaves your own phones, but enrollment takes days you can’t shorten, so start the Apple one early.

    An iPhone and an Android phone. The simulators are polite. Thumbs are not, and the two platforms differ most exactly where the simulator is least honest — keyboards, back gestures, notifications.

    A description of the first feature. Not the app; the first feature. Who does what, what they see, and every way it can end. Everything after this is mechanical; this is not.

    Step 1: write the feature as scenarios, not as a wish

    “Users can check in to a run” is a wish. A scenario is the same sentence with its endings enumerated: the user opens the app with a run scheduled today and sees a check-in button; taps it and sees confirmation with the time; taps it offline and sees the check-in queued; opens the app with nothing scheduled and sees the next run’s date. Four endings. That is the whole first feature, and it is what both platforms will be tested against.

    On the shared-logic route this isn’t a nicety; it is the input to the build. In Modaal each feature begins as a one-page spec — in the docs’ words, “the states, the transitions, and a row per ending the feature can reach” — and the executable form of that page is “Given / When / Then — a branch per ending.” A tool compiles those scenarios into fixture files, and it is those fixtures, not the prose, that both apps are later checked against.

    If you have a product document already, paste it — the new-project screen takes “your PRD or a description of the first feature.” If you don’t, the one-page requirements template produces exactly the scenario shape this step needs, and spec-driven mobile app development explains why the discipline pays off at feature twenty rather than feature one.

    Step 2: pick the template — and pick it for the life of the project

    Modaal’s new-project wizard offers two multiplatform cards, and the choice is about Android’s timing, not its possibility.

    “iPhone and Android together.” The Kotlin Multiplatform flavour: “Feature logic is one Kotlin module per feature under src-kmp/. The Android app consumes it directly,” and the iPhone app consumes the same compiled logic through a Kotlin/Native framework — one implementation of each reducer, not two. Choose it when Android is in scope now, or you want both stores at launch.

    “iPhone now, Android later.” The Swift flavour: feature logic as Swift packages, Xcode as the only prerequisite, no Gradle or Android SDK on day one. The line that makes it different from an ordinary iPhone project: “every feature is recorded and specced exactly as in the two-platform flavor.” So nothing you build now has to be rebuilt to add Android — the scenarios are already recorded.

    The docs are blunt about the one thing you can’t undo: “Pick a Duet card at creation time if Android is anywhere in the plan. The architecture a template fixes holds for the life of the project.” If Android is genuinely never — a game, a throwaway prototype — those start from other templates. iOS or Android first is the page for the timing question itself.

    Step 3: get a plan before any code exists

    This is the step that separates a demo from an app, on any builder that offers it. In Modaal the agent’s first turn on a feature is not code; it is a spec you read — overview, user scenarios, technical approach, implementation steps, files to modify, risks and open questions, testing strategy — and approve. On a two-platform project the plan is where you see, before anything is written, which parts are shared and which are per-platform screens, and where the agent thinks the platforms should differ.

    Read the risks section twice. It is where “the Android back gesture ends this flow differently” shows up as a sentence you can answer instead of a bug you find in a review. Builders that skip the plan improvise an architecture per prompt, which is fine for feature one and expensive by feature twenty — and twice as expensive with two platforms.

    Step 4: build — the logic once, the screens twice

    With the plan approved, the agent writes the feature in the order the architecture demands.

    The shared core. A pure reducer per feature: it “takes no environment — no clocks, no IDs, no network — so anything nondeterministic round-trips through an effect and comes back as an action.” This is what makes it shareable and testable: the same inputs produce the same outputs on both platforms, byte for byte. The agent adjusts it until every scenario from step 1 passes.

    Two native screens. “A SwiftUI screen on iOS, a Compose screen on Android. Each one reads state and sends actions; neither makes a navigation decision of its own.” The iPhone screen follows Apple’s Human Interface Guidelines; the Android screen follows Material 3; both are written in the platform’s own toolkit by the same agent, from the same state. This is where “views twice” costs you, and where the apps earn the word native.

    The build. The agent compiles both — Xcode for the iPhone app, Gradle for the Android app — reads its own errors, fixes them, and runs the result. You look at two phones, not at code. Everything the agent does here happens on your Mac, in a repository that is an ordinary Xcode project and an ordinary Android Studio project; there is no Modaal runtime inside the app.

    Step 5: let the build prove parity

    On the shared-screens route the two apps behave the same because they are the same code. On the two-separate-apps route they behave the same because you tested both. On this route they behave the same because the build says so.

    The mechanism is the fixtures from step 1. “tools/duet verify — the same gate CI runs. It replays every fixture on every platform and compares byte-for-byte.” If the Kotlin side and the Swift side ever disagree about what a scenario produces, the build fails on the commit that caused it. Fixtures are build products — “never edited by hand, and where a fixture and the prose disagree, the fixture is authoritative” — so the check can’t be quietly relaxed to make a red build green.

    And you don’t run any of it: “You do not have to run these commands: the agent runs them, and the CI workflow in the scaffold runs them again on every push.” What you do is read the result: green means the feature is the same feature on both phones; red names the scenario and the platform.

    Step 6: decide where the platforms should differ — on purpose

    Native on two platforms does not mean identical on two platforms; it means idiomatic on each. A share action is a share sheet on iPhone and an intent chooser on Android. A destructive action confirms differently. Navigation goes back differently. The point of writing the screens twice is that these differences are free — each screen simply does what its platform does.

    What matters is that the behaviour underneath stays one thing and that any deliberate divergence is recorded rather than accidental. Modaal keeps a ledger of the places the platforms are meant to differ, so “why does Android do this differently?” has a written answer six months later. Handling platform-specific UI on iOS and Android describes the five-layer contract that decides what is shared and what a platform may own.

    Step 7: test on both phones, then publish to both stores

    Run the app on a real iPhone and a real Android phone before anyone else does. The build proves the logic matches; only thumbs prove the screens are right. Then the two store paths, which no tool walks for you: TestFlight and App Review on Apple’s side — the App Store publishing guide covers the listing, review and the sentences Apple actually uses — and Google Play Console on Google’s, where a first release goes through the same listing-and-review shape. Keep both accounts in your own name; they are the ownership of the apps, the reviews and the users.

    Modaal produces the builds through Xcode and Gradle on your Mac. Uploading them, filling the listings and answering the reviewers is yours, and it should be — those are the parts that carry your name.

    Adding Android to an iPhone-only project later

    If you chose “iPhone now, Android later”, adding the second platform is not a rewrite; it is three moves, quoted from the docs.

    First, the foundation lands: the Kotlin core, the Android app shell and the Kotlin side of the parity workspace are added to the existing repository. Second, feature by feature: “the Kotlin reducer is written against the fixtures already recorded from the Swift one, the Android app mounts the feature, and the iPhone app moves onto the shared core for it.” The scenarios you wrote in step 1 are the specification the Android version is built to — which is why step 1 mattered even when Android was “probably, later.” Third, the manifest keeps score: “parity/manifest.yaml declares the ported side per feature, so a half-migrated repository is an ordinary, shippable state.” You can ship an iPhone update in the middle of porting.

    There is no menu item for this. “Ask the agent for Android when you want it,” and it works through the features with you, one at a time. If you are starting Android-only instead, how to build an Android app is the single-platform guide.

    The other route: building both apps with an Expo builder

    If you decided in step 0 that shared screens are what you want, the process is shorter and the tools are plentiful. Say “mobile app” in the very first prompt — Bolt’s Expo guide: “By mentioning mobile app in your very first prompt, you’ll set up the right foundation” — describe the app, and the builder writes one React Native project that runs on both phones. Test on both devices through Expo’s tooling, download the project when you want it locally, and build the store binaries through Expo’s build service; both Vibecode and Newly export a standard Expo project you can hand to any React developer.

    The steps you skip are the two-screens step and the parity step, because there is one screen and one implementation. The step you can’t skip is deciding, per platform, where the shared screen should behave differently — and that difference is written by hand, inside the shared code. For a product where the two apps should be identical and the team already lives in JavaScript, that is the right trade, and nothing on this page should talk you out of it.

    The mistakes that cost the most on two platforms

    Starting single-platform on a template that can’t grow. The architecture is fixed at creation. If Android is anywhere in the plan, pick a template that records features for it now.

    Writing the app instead of the first feature. Two platforms double the cost of a vague start. One feature, all its endings, then the next.

    Treating the plan as a formality. The risks section of the spec is where platform differences get decided cheaply.

    Testing parity by hand. If the build can prove the logic matches, let it; spend your testing on the screens, where a machine can’t judge.

    Letting the platforms drift silently. Every deliberate difference should be a recorded decision. Every undeliberate one should fail a build.

    Putting the store accounts in someone else’s name. The accounts are the product. Yours, from day one.

    Modaal is free for one project with unlimited prompts on one platform; building both platforms is a Pro feature — €9 per user per month billed annually, €15 monthly — and you bring your own AI agent subscription, so nothing is metered. The pricing page has the details, and the vibe coding guide is the wider introduction if this is your first project of this kind.

    The two Duet templates at a glance

    iPhone and Android togetheriPhone now, Android later
    Feature logic lives inKotlin modules under src-kmp/, shared by both appsSwift packages, one pair per feature
    ScreensSwiftUI + Jetpack ComposeSwiftUI (Compose added when Android is)
    PrerequisitesXcode + Android SDKXcode only
    Scenarios recorded per featureYesYes — exactly as in the two-platform flavour
    Adding AndroidAlready therePer feature, against the fixtures already recorded
    Choose whenBoth stores at launch, or Android in scope nowiPhone ships first; Android is probably, later

    From docs.modaal.dev/articles/duet, 10 September 2026. Either way the output is an Xcode project and — once Android is in — an Android Studio project on your Mac.

    Frequently asked questions

    Yes. On a React Native builder you get one codebase and identical screens on both phones. On Modaal you describe each feature once, the agent writes its logic once and a native SwiftUI screen and a native Compose screen for it, and the build replays every recorded scenario on both platforms to prove they behave the same. Building both platforms is a Pro feature; the free plan builds one.

    Each feature’s state, the actions that change it and the effects it requests are written once as a pure reducer. Each platform gets its own screen in its own toolkit — SwiftUI on iPhone, Jetpack Compose on Android — that reads that state and sends those actions. So the apps behave the same by construction and look native on each phone because they are.

    Every feature’s scenarios are compiled into fixture files. The verification step replays every fixture on every platform and compares the results byte-for-byte; a divergence fails the build on the commit that caused it. Fixtures are never edited by hand, and where a fixture and the written spec disagree, the fixture is authoritative. The agent runs it, and CI runs it again on every push.

    Yes. Modaal runs on macOS and builds the iPhone app through Xcode, which only runs on a Mac. The Android app is built on the same Mac through Gradle and opens in Android Studio. Cloud builders on the React Native route compile on their own machines and don’t need one.

    Yes, if you start on the “iPhone now, Android later” template. It records every feature exactly as the two-platform template does, so adding Android is per-feature work in the same repository: the Kotlin reducer is written against the scenarios already recorded from the Swift one, and a half-migrated project is a normal, shippable state. Ask the agent for Android when you want it.

    The project on your Mac is an ordinary Xcode project and an ordinary Android Studio project, in Swift and Kotlin, from the first build. There is no Modaal runtime in the app and no export step — any iOS or Android developer can open and continue it. Duet, the framework the shared core uses, is open on GitHub and currently pre-release.

    When you want the two apps to be identical, your team already writes React, and the product is forms, lists and feeds rather than something that leans on new platform frameworks. Vibecode, Newly and Bolt with Expo all produce a standard Expo project you can download. The trade is that the screens follow one set of conventions on both platforms and the logic reaches the phone through a bridge.

    No. Modaal produces the builds through Xcode and Gradle on your Mac; uploading them, writing the listings and answering the reviewers is yours, with both store accounts in your own name. The App Store publishing guide walks the Apple side step by step.

    Start free. Ship native.

    One project, unlimited prompts. No card.

    Keep reading