Decision (worked example, not your company). For a company with two mature native apps, about forty mobile engineers and a platform-feature roadmap, the call is: stay native on both platforms, and share business logic in one bounded domain through Kotlin Multiplatform only if a measured duplication cost justifies it. Alternatives rejected for now: a full React Native or Flutter rewrite, and Compose Multiplatform for all UI. Revisit trigger: the cost of keeping two implementations in step, measured as parity bugs and feature lead time, rises above what we write down in the ADR, or a platform dependency (hiring, a vendor SDK, a platform release) changes the assumption in section 4. Every claim below is marked Verified (checked in a primary source on 13 Oct 2026) or Opinion.
1. Why this is a decision record, not a debate
Most "native vs cross-platform" pages compare frameworks. A staff engineer is asked a different question: who decides, on what evidence, what would make us reverse, and what happens to the people and libraries left behind. That is what an architecture decision record (ADR) captures, and it is the part a framework comparison skips.
The topic is also moving. On 10 Sep 2026 Shopify published a post saying it is moving its mobile apps from React Native back to Swift and Kotlin. The company chose React Native in 2020, and its own post says the original goals were met. What changed, per the post, is the cost of building twice, because AI coding agents now do much of the implementation, cross-platform translation, testing and review. [Reported by Shopify Engineering, 10 Sep 2026; read via a page summary.] Gergely Orosz's coverage (The Pragmatic Engineer, 29 Sep 2026) reports that Shopify had moved all six of its mobile apps to React Native by 2025, and adds caveats: Shopify's claims are largely self-reported, the migration is not finished, and native still means maintaining two platforms. [Reported by The Pragmatic Engineer; the article is paywalled and only the part before its fourth section heading was readable on 13 Oct 2026.]
Read that as one data point about one assumption (the cost of a second codebase), not as a verdict. A decision record is how you keep the assumption visible so the next reversal, in either direction, is cheap to argue.
2. What the call looks like
The ADR template in section 10 has a slot for each part: the call, at least three alternatives (including "do nothing" and "hybrid"), weighted criteria, the assumption that would change the decision, a revisit trigger with a date, and the reversal cost. [Opinion] The most common failure is a record with a call and no revisit trigger, which makes a reversible bet permanent by default.
3. Criteria, weighted by company situation
Pick weights from your situation and write them down before scoring the options.
| Criterion | Weight up when | Weight down when |
|---|---|---|
| Team shape and hiring | You have strong native teams, or hiring native engineers is easy in your market | Your engineers are mostly web, or you can staff only one platform |
| Platform-feature lag | Your roadmap leans on new OS features, widgets, system integrations | The app is mostly forms, lists and content |
| Performance ceiling | Heavy animation, camera, media, large lists, tight startup targets | CRUD-style screens with network latency dominating |
| SDK and native-module needs | You depend on vendor SDKs (payments, maps, ads, identity) that ship native first | The needed SDKs already have maintained wrappers |
| Code and test sharing | Business rules are complex and must behave identically on both platforms | Each platform's behavior is intentionally different |
| Time to first release | A deadline matters more than the year-three cost | You are optimizing for a five-year horizon |
| Dependency surface | You cannot staff library maintenance | You can fund and own your own forks |
| Exit cost | The product may change shape within two years | The product is stable |
[Opinion] The table is our synthesis; no source we read publishes weights. One verified detail fits the dependency row: Shopify's own 2025 retrospective, as quoted by Orosz, notes that the React Native framework is less comprehensive than native, so teams leaned on more third-party libraries. [Verified, secondhand through the Pragmatic Engineer article, 13 Oct 2026.]
What the options are today (official status, 13 Oct 2026)
The landscape changes quickly, so each line carries a date and a source.
- Kotlin Multiplatform (KMP). Google's Android documentation says it officially supports KMP for sharing business logic between Android and iOS, and calls it stable and production-ready; it says you choose how much to share, from core logic to the whole app, and that you can start without rebuilding the app. It lists many Jetpack libraries with KMP support, including Room, Paging, DataStore, Lifecycle and Navigation, with platform coverage that varies by library. [Verified: developer.android.com/kotlin/multiplatform, 13 Oct 2026.]
- Compose Multiplatform. The Kotlin documentation's supported-platforms page lists the UI framework as stable on Android, iOS and desktop (JVM), and beta on web (Kotlin/Wasm). [Verified: kotlinlang.org supported-platforms page, 13 Oct 2026.] The same page lists core KMP as stable for Android and iOS.
- React Native. Its documentation says the New Architecture (JSI instead of the old bridge, concurrent rendering) is on by default in new projects since version 0.76, and that enabling it does not make an app faster by itself. [Verified: reactnative.dev architecture page, 13 Oct 2026.] Orosz's article reports modest gains for Shopify from the move: about 10% faster Android startup and about 3% on iOS, with some complex screens slower until tuned. [Verified, via the Pragmatic Engineer article.]
- Flutter. The docs front page announced Flutter 3.47 when read, and the page was last updated 28 Aug 2026. Its platform-channel page says Dart talks to native Kotlin/Java and Swift/Objective-C through asynchronous message channels that are not type-safe, with the Pigeon package generating type-safe code. [Verified: docs.flutter.dev, 13 Oct 2026.]
Not verified here: any cross-framework performance comparison. We did not read a benchmark we trust, so this guide makes no claim about which framework is faster. Measure your own screens (section 9).
4. The assumption that would change the decision
Every decision rests on one or two assumptions that are cheaper to monitor than to re-litigate. Name them.
The Shopify case is instructive because the post, as we read it, does not claim React Native failed. It says React Native delivered what it was adopted for: building features once, letting developers without mobile backgrounds contribute, and less time spent on parity. Its costs also grew: performance tuning, framework upgrades and dependency management. What changed was the price of the second codebase. [Reported by Shopify Engineering, 10 Sep 2026; read via a page summary.] In record form, the 2020 decision had an unwritten assumption, "writing both platforms costs more than sharing", and the 2026 reversal rests on that assumption failing.
For your record, write the assumption in this shape:
- Assumption: "Building and keeping two native implementations costs more than owning one cross-platform stack, measured as engineer-weeks per feature."
- Evidence today: the number, with its date and source.
- Trigger: "If the measured ratio crosses X, or a named capability ships, reopen this record."
- Owner and date: a person and a review date, not a team.
[Opinion] The tempting mistake in late 2026 is to swap one untested assumption for the opposite one. "Agents make two codebases cheap" is, today, a claim reported by the company making the move, with the migration unfinished. Treat it as a hypothesis to measure on one app first, which is what a pilot (section 6) is for.
5. Greenfield or brownfield
Two migration shapes, and the choice is part of the decision.
Brownfield: convert in place, screen by screen or module by module. It keeps the app shipping, but you live with two worlds in one binary, interop code and two navigation models for months. KMP is designed for this shape: Google's page says you can share logic without a complete rewrite. [Verified: developer.android.com, 13 Oct 2026.] Interop between the two worlds is where the surprises accumulate.
Greenfield: build the new app beside the old and cut over. Shopify chose this, per its post, because agents can port features using the existing React Native app as a reference and a clean slate avoids legacy constraints. It did the Shop app first (proof of concept to a published native app in 12 weeks per the post), is working on the Shopify app (over 300 screens, expected later in 2026), and plans the rest after. [Reported by Shopify Engineering, 10 Sep 2026; these are the company's own statements and the work was in progress when read; read via a page summary.]
A usable rule of thumb [Opinion]: choose brownfield when the existing app is healthy and the pain is local (one domain duplicated, one team blocked). Choose greenfield only when the old stack itself is the problem, and you can afford to run both for a quarter or more. For both, start with the smallest app that has real users, as Shopify did with the Shop app, and write the expected duration before you start so you can notice when it slips.
6. Parity testing
When two implementations must behave the same, the tests are the contract. The Shopify post gives the pattern at the high end: each rebuilt screen has to pass tests, match the running app visually, pass review steps and get human approval before the next one starts; business logic was re-architected to be decoupled from UI so it can run headlessly. [Reported by Shopify Engineering, 10 Sep 2026; read via a page summary.] Orosz reports the shared business-logic test suite as central, because it gives agents fast feedback. [Reported by The Pragmatic Engineer, 29 Sep 2026 (paywalled; opening sections only).]
You do not need agents to use the idea. A parity setup for a staff-level plan:
- Shared behavior spec. Express business rules as data-driven test cases (inputs and expected outputs) in a neutral format, run by both platforms' test suites. A bug found on one platform becomes a case for both.
- Contract tests at the boundary. If logic is shared through KMP, test the exported API from Swift as well as from Kotlin, because that is how iOS developers will call it.
- Screenshot comparison per screen. Tolerant of platform differences you accept, strict on the ones you do not. Write the accepted differences down.
- Instrumentation parity. The same events and the same names on both sides. A funnel that differs by platform in its definition is not a measurement.
- A pilot with a rollback. Ship the first migrated screen to a small cohort and compare metrics against the old screen (section 9).
[Opinion] Parity tests are cheap to start and expensive to retrofit. Write the first ten cases before you write the first migrated screen.
7. Who owns the orphaned libraries
Any framework bet leaves libraries behind: community forks, internal modules, open-source projects you started. Reversing the bet turns them into orphans unless someone decides otherwise.
Shopify's post shows the full range of outcomes, and it is a good checklist. Skia for React Native is sponsored through the end of 2026, with its maintainer planning a fork under a new name and the original repository to be archived after the transition. FlashList, described as downloaded about two million times a week, gets critical compatibility fixes from Shopify while it talks to companies about long-term stewardship. Restyle is archived, kept functional through the end of 2026, and forks are welcome. [Reported by Shopify Engineering, 10 Sep 2026; read via a page summary.]
Three outcomes there: sponsor a handover, seek stewards, archive with notice. For an internal or open-source library of your own, decide each one in the record:
- Who are the users, inside and outside the company? Count them if you can.
- Who owns it after the migration, by name? "The platform team" is not a name; it is a risk.
- What is the sunset date, and what do consumers do on that date?
- What is the exit path: fork, transfer, archive, or keep funding.
[Opinion] Put the library question into the decision, not after it. A rewrite plan that does not say what happens to the shared libraries has hidden its cheapest-to-defer cost. Link the pattern to our pagination library case study, which treats deprecation windows, binary compatibility and version-adoption telemetry as part of a library's design, and to the build-versus-adopt question in the image loading case study.
8. Rollback and reversibility on mobile
A web deploy can be reversed in minutes. A mobile release cannot, and that changes how you plan a framework change.
What you can undo
- Stop a rollout. On Google Play, pausing a staged rollout stops the update from reaching more users, and you can raise the percentage manually. [Verified: Play Console help, 13 Oct 2026.] On the App Store, a phased release for automatic updates runs over seven days at 1, 2, 5, 10, 20, 50 and 100 percent, and can be paused for up to 30 days in total. [Verified: App Store Connect help, 13 Oct 2026.]
- Turn a feature off. Server-controlled flags let you disable a code path without a store release, if the code path was shipped with a flag. [Opinion, standard practice.] See the kill-switch discussion in our analytics SDK and push SDK case studies.
What you cannot undo
- Users who already have the build. Google's help page says anyone who already received the update keeps it when you halt, and it does not describe a rollback option; its suggested fix is a new release with a corrected bundle. [Verified: Play Console help, 13 Oct 2026.]
- Manual updaters. Apple's page says people who update manually are not held back by the phased schedule. [Verified: App Store Connect help, 13 Oct 2026.]
- Old clients. Users who never update keep running old versions, so any server contract, data format or deep link you change must keep working with them. The push SDK case study designs for this. [Opinion.]
- On-device data. A migrated database or a changed cache format cannot be reverted by turning a flag off. The new build must be able to read what the old build wrote, and the reverse if you plan a rollback by shipping the old code again. [Opinion.]
The rollback ladder for a framework migration [Opinion, our synthesis]:
| Step | Reversal | Cost |
|---|---|---|
| Per-screen flag, both implementations in the binary | Server flag flips back within minutes for users on the build | Binary size and double maintenance for the flag period |
| Staged rollout of the new app version | Pause the rollout; users already on it stay on it | Fast to stop, slow to fully recall |
| Ship the old code as a new release | A new store release; the version number moves forward, not back | Days, plus review and rollout time |
| Cutover of a greenfield app | Keep the old app in the stores for a stated period | Two apps to maintain |
Decide how long each rung stays available before you start, and write it in the record. The cheapest time to define "we are not going back after date D" is before D.
9. What to measure and the guardrails
A migration needs one success metric, a few guardrails and a stopping rule, chosen before the first cohort.
- Success metric. The one that justified the decision: for example feature lead time (merge to production), parity bugs per release, or engineer-weeks per feature across both platforms. Define it so you can read it before and after.
- Guardrails. Metrics that must not get worse: crash-free sessions, ANR and hang rates, cold start, jank, app size, and the conversion step of the migrated screen. For definitions and field data, use the Android vitals and Apple MetricKit documentation rather than a blog's thresholds. [Opinion: we did not verify specific vitals thresholds for this guide, so none are quoted.]
- Comparison method. Compare the migrated screen against the old screen in the same cohort window, and watch for the effect Meta's 2013 Messenger post warned about, an unexplained improvement that turns out to be a logging change. [Reported in an earlier research pass, not re-fetched today: Facebook Engineering, 13 Nov 2013; old, so use it as a pattern.]
- Stopping rule. "If crash-free sessions on the migrated cohort drops by more than the tolerance we wrote down, we pause the rollout." Write the tolerance; do not leave it as "meaningfully".
- Cost side. Track engineer-weeks, and compare with your estimate at the 25%, 50% and 75% marks. A migration whose estimate has no checkpoints is a bet you will only learn the size of at the end.
For a model of a measured adoption decision, the Uber Android team's Kotlin build study is a good template: many experiments across configurations, a survey of engineers, and guidance with caveats rather than a flat yes or no. [Reported in an earlier research pass, not re-fetched today: Uber Engineering, 30 Apr 2019.] Link the measurement shape to our staff mobile loop guide, section 4.
10. A fill-in ADR template
Copy this, replace each bracket, and keep it in the repository next to the code. It is free to use and adapt.
# ADR-[number]: [Short title of the decision]
Status: [Proposed | Accepted | Superseded by ADR-[n]]
Date: [YYYY-MM-DD] Review date: [YYYY-MM-DD]
Owner: [one named person] Deciders: [names]
## Context
- Product and users: [what the apps do, platforms, rough size]
- Team shape: [engineers per platform, hiring plan, skills]
- Current stack: [native / cross-platform / mixed, versions]
- Pressure: [what triggered the decision]
## Decision
We will [the call] for [scope] for the next [N] years.
## Alternatives considered
1. [Option A]: [one-line summary, why not]
2. [Option B]: [one-line summary, why not]
3. Do nothing / hybrid: [what it costs]
## Criteria and weights (set before scoring)
| Criterion | Weight (1-5) | Why this weight |
|---|---|---|
| [e.g. platform-feature lag] | [n] | [reason] |
## Key assumption (the one that would change this)
Assumption: [statement]
Evidence today: [number or fact, date, source]
Trigger to reopen: [metric or event]
## Migration path
Greenfield or brownfield: [choice and why]
First scope (smallest real app or module): [what]
Expected duration: [weeks], checkpoints at [25% / 50% / 75%]
## Parity and testing
Shared behavior spec: [where it lives]
Accepted platform differences: [list]
Instrumentation parity: [event names reviewed by whom]
## Libraries and ownership
| Library | Users | Owner after change | Sunset date | Exit path |
|---|---|---|---|---|
| [name] | [count or list] | [person] | [date] | [fork / transfer / archive] |
## Rollout and rollback
Rollout: [flags, cohorts, store staged rollout settings]
Old clients: [versions that must keep working, until when]
Rollback ladder: [steps and the date after which each is no longer available]
## Measures and guardrails
Success metric: [definition, baseline, target, date]
Guardrails: [crash-free, ANR/hangs, cold start, jank, size, conversion] with tolerances
Stop rule: [exact condition and who calls it]
## Consequences
Good: [list]
Bad: [list]
What we are choosing not to know yet: [list]
## Communication
Execs: [one-paragraph summary and the ask]
Engineers: [where to discuss, who decides]
11. Android and iOS specifics
- Android. Google documents Android Studio support and a migration route for adding KMP to an existing app, and lists the Jetpack libraries with KMP support; check which platforms your library version covers. [Verified: developer.android.com, 13 Oct 2026.]
- iOS. Shared Kotlin reaches Swift through a generated framework, so the API Swift developers see is part of your design. [Opinion; the Apple and JetBrains interop docs were not read for this guide.] The Kotlin compatibility page lists Xcode 26.4 in its newest row, a reminder that toolchain versions are a recurring cost. [Verified: kotlinlang.org compatibility guide, 13 Oct 2026.]
- Stores. Apple pauses a phased release for up to 30 days in total; Google lets you pause and set the percentage by hand (section 8).
12. A spoken outline of a staff answer
Use this as a shape for a conversation about the decision, in a design discussion or any other setting where you are asked to defend a framework choice. It is our synthesis and not a script for any specific process. We do not know what any company asks.
- Frame (30 seconds). "I want to treat this as a decision we will have to defend and possibly reverse, so I will state the call, the alternatives, and what would change my mind."
- Ask. "How many apps and engineers, which platforms, how heavy on platform features, how is hiring going, and what triggered the question?"
- Weights. "For a team shaped like this I would weight platform-feature lag and SDK needs high, and time to first release low. Tell me if the weights are wrong, because the answer moves with them."
- The call and the alternatives. "My call is [X]. I considered [Y] and [Z], and did not choose them because [reason tied to the weights]."
- The assumption. "The assumption I am least sure about is [cost of a second codebase, or hiring, or a vendor SDK]. I would put a number on it and a date to check it."
- Migration. "Brownfield or greenfield, smallest real scope first, with an estimate and checkpoints."
- Parity and ownership. "A shared behavior spec runs on both platforms. Each library gets a named owner and a sunset date."
- Rollback. "Flags per screen, staged store rollout, and an honest list of what I cannot undo: users who already have the build, manual updaters, old clients and migrated local data."
- Measure. "One success metric, guardrails with tolerances, a stop rule, and a cost checkpoint at a quarter, half and three quarters."
- Close. "I would write this as an ADR with a review date, so the next person can see why we chose it and when to reopen it."
[Opinion] The close matters most. Seniors tend to stop at the call; the written, dated, reversible record is what makes the answer staff-level.
13. What we do not know
- Shopify's apps were unfinished and its numbers self-reported, so the outcome is unknown. The assumption that agents lower the cost of two codebases is the company's, repeated by Orosz, and untested elsewhere.
- Airbnb's 2018 React Native retrospective is widely cited, but the primary pages were not read, so we use none of its details.
- We make no claim that one framework is faster than another, and we do not know what any company asks in interviews.
14. Where to go next
- Staff mobile loop guide, image loading, pagination library, photo backup and file sync (shared logic across clients, protocol changes and old versions), and the mobile system design framework.
Which migration or platform question would you like worked through next? Open an issue on the site's GitHub repository with the situation, and we will consider it for the staff track.
Verification notes (last reviewed 13 Oct 2026)
on 2026-10-13 these were re-fetched and read via the fetch tool, whose output is a model-written summary. Paraphrased from page summaries; read the linked pages for exact wording: Shopify Engineering, "Back to native" (shopify.engineering/back-to-native, dated 10 Sep 2026: decision, reasons, greenfield approach, 12-week Shop app, 300+ screen Shopify app, parity checkpoints, Skia, FlashList and Restyle outcomes); The Pragmatic Engineer, "Why has Shopify dropped React Native?" (29 Sep 2026; paywalled and truncated after its fourth section heading, so only the opening sections were read; the numbers on startup gains, six apps and the quoted 2025 retrospective come from that part); developer.android.com/kotlin/multiplatform (KMP support statement, Jetpack library list); kotlinlang.org supported-platforms page and compatibility guide (Compose Multiplatform and KMP status, Xcode row, page date shown 8 Oct 2026 but not labelled as an update date); reactnative.dev architecture page (New Architecture default since 0.76); docs.flutter.dev and its platform-channels page (Flutter 3.47, last updated 2026-08-28); Google Play Console help answer 6346149 (halt, adjust, no rollback described); and Apple's App Store Connect page on phased release (seven days at 1, 2, 5, 10, 20, 50, 100 percent, 30-day pause, manual updaters). The Meta 2013 Messenger post and Uber 2019 Kotlin study come from the earlier research pass in research/10-staff-track.md, not re-fetched today, so re-check them if you want them as verified. Not verified: Airbnb 2018 (primary not read), any performance comparison between frameworks, Android vitals and Apple MetricKit thresholds (none quoted), the Apple and JetBrains docs on the Swift-Kotlin interop surface, and the claim that old app versions persist (stated as opinion; check store analytics for your own app).Our synthesis, not sourced: the weighted criteria table, the rollback ladder, the greenfield-versus-brownfield rule of thumb, the parity checklist, the ADR template and the spoken outline. Shopify and Pragmatic Engineer facts are fast-moving; re-fetch on publish day and recheck in 90 days. The Android and iOS sections have not yet been reviewed by platform specialists. No company's interview content is claimed anywhere.
Keep reading
Follow new case studies
Subscribe with any RSS reader. Found a mistake? Open an issue on GitHub.
New case studies appear in the feed first. Any RSS reader works.