All articles
October 5, 20263 min read#flutter#react-native#migration#architecture

Migrating a Production App from React Native to Flutter Without Losing Users

What I learned leading two React Native to Flutter migrations (Mengantar and SRX Property): how to sequence the rewrite, keep releases flowing, and avoid the traps that stall most migrations.

I have now led two full migrations from React Native to Flutter on apps that people depend on every day: the Mengantar logistics platform and the SRX Property app in Singapore. Both shipped. Both kept their users. Neither was painless. This is the playbook I wish someone had handed me before the first one.

Why migrate at all

Nobody should rewrite a working app for fun. In both cases the trigger was the same combination: a React Native codebase that had drifted across several major versions, native modules nobody wanted to touch, and a team that was already comfortable with Dart. The business case was not "Flutter is faster". It was "we spend 30% of every sprint fighting the toolchain, and that number is growing".

If you cannot write down the cost you are paying today in a sentence a product manager understands, do not migrate yet.

Decide the shape before the first line of code

The biggest mistake I see is starting the Flutter project as a blank flutter create and porting screens one by one in whatever order the backlog suggests. Three months later you have a second app that is 60% done and a first app that still needs bug fixes.

Instead, spend the first two weeks on three things:

  1. Inventory every screen, every API call, and every native capability. Push notifications, deep links, camera, background location, payments. Native capabilities are where migrations die, so list them first.
  2. Settle the architecture. I use a modular Clean Architecture: one package per feature, a shared core package for networking and DI, and a thin app package that wires routes. The structure is boring on purpose. Boring scales.
  3. Pick the state management once. BLoC for anything with real business logic, Cubit for simple screens. Do not let each feature choose its own.

Port the plumbing first, screens second

The order that worked on both projects:

  • Networking layer, auth, token refresh, and error mapping.
  • Analytics and crash reporting. You want Crashlytics and your analytics stack live from the first internal build, not the last.
  • Deep links and push notifications, because they touch every platform config file.
  • Then the single most used screen in the app.

By the time the first real screen exists, the hard parts are already proven on both platforms.

Keep the old app shipping

A migration that freezes the current app for six months is a migration that gets cancelled. On Mengantar we kept a small rotation on the React Native app for bug fixes and critical features while the rest of the team built the Flutter version. Every feature that landed in the old app went into a shared checklist so it would not be forgotten in the new one.

Release behind a rollout, not a big bang

Both apps went out as a new build under the same package name, rolled out in stages on Google Play and through phased release on the App Store. Watch three numbers for the first week: crash-free rate, session length, and the conversion of whatever your core action is. If any of them drops below the old app, pause the rollout and fix it. A paused rollout is cheap. A one-star review wave is not.

What I would do differently

  • Snapshot-test the old UI. We eyeballed parity. Screenshots of every old screen, organised by feature, would have saved days of "wait, was there a button there?"
  • Migrate analytics event names verbatim. We "cleaned up" event names during the move, and the data team spent weeks stitching dashboards back together.
  • Write the scaffolding tool earlier. The Rust CLI I built later to generate feature modules would have paid for itself during the migration.

A migration is a product project with an engineering deliverable, not the other way round. Treat it that way and it ships.

Written by
Ghozi Mahdi
Senior Mobile Engineer · Founder, Rancangin Aja
Get in touch