Artificial Intelligence
React Native Is Dead. Let AI Keep Your Swift and Kotlin Apps in Sync.
By Daniel Rice · · 5 min read
React Native was a staffing decision dressed up as an architecture. One JavaScript codebase, two app stores, and a team that did not have to hire both a Swift engineer and a Kotlin engineer. Flutter made the same offer with a different language. The tax was a third platform sitting between the product and the phone. AI removed the reason to pay it.
Build the iOS app in Swift. Build the Android app in Kotlin. Keep them in feature sync with an agent that can read one implementation and produce the other, then make a person review both diffs. The shared thing is the behavior. The UI is allowed to be a native iOS screen and a native Android screen.
The framework was a compromise about people
A cross-platform toolkit is rational when typing the feature twice is the expensive part. For a decade, that was true. A small team picked React Native or Flutter so a login screen, a list, and a settings page did not have to be written by two specialists. The framework vendor promised that the next OS release would mostly be their problem.
That bargain had a shape. You accepted bridge bugs, delayed access to new platform APIs, and screens that felt slightly wrong on both phones. You also accepted an upgrade treadmill that belonged to the framework, not to Apple or Google. When camera behavior, background work, or a new permission dialog mattered, someone still dropped into native code, and the "one codebase" story got a hole in it.
Flutter is the same bet. A different language and a drawn UI do not change the deal. You are still maintaining a platform that is not the one your users run.
AI moves the expensive part
The scarce resource is no longer a person who can type both languages. Models have read an enormous amount of public Swift and an enormous amount of public Kotlin. They are good at the boring translation: this screen has these states, this request hits this endpoint, this error message is this string. A feature that used to be a second team's week is a reviewed diff.
What stays expensive is the product decision. What does the screen do when the list is empty, the network fails, or the user denies the permission? Write that down once. Implement it in the language of the platform you are holding. Hand the agent the iOS change, the API contract, and the Android project, and tell it to match the behavior. Then run the Android tests and read the Kotlin.
The loop runs the other way too. An Android fix for a timezone bug is a prompt against the Swift project, with the same test named on both sides. Sync is a habit you run on every feature, not a quarterly project where someone discovers the apps drifted.
Sync the behavior, and let the phones look like phones
"Perfectly in sync" does not mean a single layout tree forced onto both operating systems. That goal is how teams end up back in a framework, sanding off the differences that make an app feel finished. iOS and Android have different navigation, different type, and different expectations for back, sheets, and settings. Meet those expectations.
The checklist that has to match is the one a user would notice if it broke:
- The same accounts, roles, and empty states.
- The same copy, prices, and error messages.
- The same API calls, including the failure paths.
- The same rules for what works offline and what requires a permission.
A tab bar on iOS and a navigation bar on Android can both pass that list. A shared JavaScript renderer is not required for them to pass it.
A workflow that survives a real feature
Treat one platform as the specification for the other, and keep the specification executable.
- Describe the feature in a short note: user-visible behavior, edge cases, and the API it calls. If the backend is Rails, the request and response shape belong in that note.
- Implement it on one platform with the tests that lock the behavior. SwiftUI and UIKit are both fine. Jetpack Compose and the view system are both fine. Pick the current native default and stay there.
- Point the agent at that change and at the other project. Ask for the same behavior in Kotlin or in Swift, using that platform's controls. Ask it to add the matching tests, not a screenshot tour.
- Run each app's tests. A failure on one side is a failed sync, even if the other side is green.
- A person reads both diffs. The agent is fast at the translation. It is also capable of "fixing" a platform convention that was not broken.
Do this when the feature is small. A month of iOS work ported in one prompt is how the apps look synced in a demo and diverge in production. Small, repeated ports stay reviewable. That is the same rule that makes AI useful in a Rails app: short diffs, real tests, a human at the end.
What you give up, on purpose
You give up the fantasy of one pull request that changes both stores. You have two repositories, or two targets with two review habits. Release trains can still ship the same week. They do not have to share a binary.
You also give up a class of bugs you used to budget for. No bridge. No framework release that blocks an OS release. No plugin that wraps a platform API you could have called directly. When Apple or Google ships a capability, the app that is already written in the platform language picks it up without waiting for a binding.
Hiring changes with it. You still want someone who can tell good Swift from a plausible imitation, and someone who can do the same for Kotlin. Those can be the same senior person plus an agent. They do not have to be two full teams retyping every screen by hand. The framework was hiring a mediator. You can stop.
Where a shared UI toolkit still gets picked
A throwaway prototype, built to learn whether anyone wants the product, can be whatever is fastest to delete. If that prototype is React Native, delete it when the product is real. Carrying it into the App Store and Play Store is how a temporary shortcut becomes the architecture.
A team that will never touch the camera, notifications, widgets, or offline storage can also keep a toolkit and be fine. Most products that matter eventually touch one of those. Plan for the version of the app you intend to still be shipping in three years.
The contract lives on the server
Two native clients stay in sync more easily when they are not inventing behavior. The rules that must match belong in the API: who can see a record, what a valid write is, which errors exist. A Rails app is a good place for that contract. The iOS app and the Android app become two clients of the same decisions, and the agent has a spec that is a response body rather than a Slack thread.
When a product needs both phones and a Rails backend, we keep the server as the shared source of truth and the clients native. Rails Agency will build the API so a Swift screen and a Kotlin screen can implement the same feature without a framework in between.
FAQ
- Should a team still start a new app on React Native or Flutter?
- Build the iOS app in Swift and the Android app in Kotlin. Use an AI workflow to port each feature to the other platform and review both diffs. A cross-platform toolkit still fits a prototype you plan to throw away.