As one of the fastest growing mobile apps in the world, the Shop app serves hundreds of millions of customers and millions of merchants. Since we introduced it in 2020, Shop app has been at the forefront of Shopify’s wider investment in React Native, adopting the framework from its inception. That decision has served Shop app well, but our increasing ability to develop with coding agents has allowed Shop to go fully native, building with Swift and Kotlin.
Why the move
We wrote in depth about our decision to go native, but the TL;DR is that advances in coding agents changed the tradeoffs behind maintaining a shared mobile codebase. Building separately for iOS and Android still has costs, but agents made it reasonable to reconsider that decision.
For the Shop App, this coincided with our next major React Native investment: adopting the New Architecture. That work would have required us to revisit native module integrations, rendering, and the boundaries between shared and platform-specific code. Before committing to this investment, we tested whether coding agents could help us build directly in SwiftUI and Jetpack Compose while keeping product behavior aligned across platforms.
The proof of concept
Before committing to a full migration, we ran a small proof of concept. One engineer spent a week working with coding agents to migrate as much of the existing React Native app as possible into a native iOS app built with SwiftUI.
The results were compelling. Using the existing React Native app as a reference, we were able to recreate screens, interactions, and application flows quickly. The result wasn’t production-ready after just one week, but it demonstrated that a close, feature-for-feature migration was achievable and gave us confidence to pursue a full migration.
Agents were particularly effective when they had an existing implementation to work from. They ported defined features, scaffolded screens, wired up data, implemented animations, and refined layouts based on visual feedback.
What we accomplished
After we made the decision to migrate, a core group of six engineers built the native foundations and the app’s main user journeys. Feature teams joined midway through the migration to validate their areas and cover edge cases. Our priority was preserving both the behavior of the features we ported over and the analytics events that downstream systems depended on.

The migration needed to feel like a normal app update for existing users: they should remain signed in and continue receiving push notifications. Interactions also needed to emit the expected events, with the context required by downstream systems, like recommendations. While preserving that continuity, we also used the migration to simplify the app, deliberately retiring some screens and streamlining others.
We compared the native apps with their React Native predecessors across startup time, session stability, app size, build time, and rendering performance.
Startup time
These recordings compare cold starts of the native iOS and Android apps with their React Native counterparts. Time is measured from tapping the app icon until the initial home feed content is visible.
| Platform | Native | React Native | Startup time reduction |
|---|---|---|---|
| iOS | 2466 ms | 3200 ms | 23% |
| Android | 2233 ms | 4433 ms | 50% |
Session stability
The native releases recorded higher stability rates than React Native. Our historical stability was at 99.5%+ but with the release of the native version, our session stability has climbed to 99.95%+ — a 10x reduction in sessions that crash.
App size
The native Android release build was substantially smaller than its React Native counterpart, shrinking by 109 MB. On iOS, the release build was similar in size, increasing by 1 MB.
| Platform | Native | React Native | Difference |
|---|---|---|---|
| iOS | 68 MB | 67 MB | +1 MB (+1.5%) |
| Android | 184 MB | 293 MB | −109 MB (−37.2%) |
Build time
Android release build time fell approximately 75%. On iOS, release builds are taking about the same amount of time. The benefit of build time improvements include being able to test out new builds faster as well as consuming less computational time.
Android runtime performance
In this recording, the native Android app reaches 120 FPS while scrolling the feed and navigating between screens on a Pixel device. This has been accomplished with very little optimizations so far, underscoring a critical improvement in performance.

What we’ve learned
Agentic development workflows differ from traditional ones. Rather than having one developer make a change, run the app, and iterate, we now often run multiple agent sessions across separate worktrees. For this migration, we focused on giving agents clear tasks, maintaining fast build and test loops, and reviewing changes frequently.
We built a reusable migration workflow as an extension for the Pi coding agent. Specialized subagents inspected the React Native source, documented its behavior, prepared platform plans, implemented features, and reviewed parity. The source review covered UI, state, navigation, analytics, accessibility, and data behavior. Engineers reviewed requirements and plans before proceeding with implementation. Plan acceptance was tied to a hash of its contents: changing a plan invalidated its previous acceptance. This kept approval attached to the implementation plan that had actually been reviewed.

Agents also needed feedback from the running app. We built Tardis, a debugging tool to give them structured access to live native app events, logs, and state, along with the ability to send commands to the app. Agents could use that feedback to investigate issues, check navigation and analytics, and validate fixes with less manual UI inspection.

To support parity reviews, we added a Tardis feature that captured screenshots and event windows from the React Native and native apps at named checkpoints. With raw event capture enabled, agents could compare event names, counts, and payload fields. The comparison instructions accounted for values that naturally differed between runs, like timestamps and page UUIDs, while checking the relationships between events and their page or entity context. This gave our engineers a repeatable way to investigate discrepancies in the flows they exercised.
Native expertise remained essential. Generated code could satisfy feature requirements while still introducing duplication, architectural drift, or performance problems. Repository guidance helped, alongside linting, tests, static analysis, performance checks, and code review.
The learning curve was nevertheless more manageable than we expected: familiar declarative UI concepts helped React Native engineers become productive in SwiftUI and Jetpack Compose, while platform knowledge guided our architectural decisions and reviews. A simplified version of our Avatar component illustrates that shared structure.
React Native
SwiftUI
Jetpack Compose
These simplified examples use our Gravity design system. Imports, image loading, and accessibility details are omitted.
Where we go from here
One of the key principles we established when undergoing this migration was that Android and iOS must be at feature parity at all times. React Native enforced this through a shared codebase, and now that we’re building natively we’ll continue to enforce this through our development and release process.
We know there’s still more polish and improvements we can make to our performance. This first version raised the ceiling of where we can go as a mobile app, and we see it as the baseline that we’ll improve from. As we continue to evolve our tooling and development process, we hope to share more learnings here in the future.


