We shipped our first mobile application this quarter. It is live on both iOS and Android, and it is in the hands of real users. Here is the honest account of how we got there.
Why React Native
The client needed iOS and Android. Writing two native codebases was not feasible within the project budget or timeline. The options were React Native or Flutter, and we leaned toward React Native for one practical reason: our team already writes React for web work, and the mental model transfer is real. It is not zero work to move from web React to React Native, but it is significantly less than starting from a new paradigm.
We did our research before committing. The core trade-off with React Native is well-documented: you get a shared codebase and a JavaScript-first development experience, and you accept that you will occasionally hit a wall where you need platform-specific native code. We read through recent community discussions on that wall and concluded it was manageable for the use case we were building - a data-driven application with standard UI patterns, not a graphics-heavy or hardware-intensive app.
What we did not fully appreciate beforehand was how much the React Native ecosystem had improved in the two years prior. Navigation, state management, and the development tooling had all gotten meaningfully better. Our early research had primed us for more pain than we encountered.
What Surprised Us During the Build
Three things we did not expect.
Platform behavior differences are more subtle than we anticipated. The same component can behave slightly differently on iOS versus Android in ways that are not obvious until you test on both. Text rendering, scroll behavior, keyboard handling - small variations that accumulate. Our testing process needed an explicit cross-platform pass on every screen before we marked anything done.
Over-the-air update capability changed how we thought about releases. Shipping updates through a push mechanism rather than an app store submission cycle meant that small fixes could reach users without a review delay. This sounds obvious but in practice it shifted how we scoped post-launch maintenance for the client. Bug fixes could happen faster, which changed the risk calculus on what we shipped at launch versus what we fixed afterward.
The navigation architecture decision mattered more than we expected. We made our navigation choices early and they constrained us later when the client added a flow we had not anticipated. Stack navigators and tab navigators compose in ways that can get complicated fast. We now spend explicit time in the scoping phase on navigation architecture, which we had not done before.
How This Shaped Our Mobile Scoping
The main change in how we now scope mobile app development work: we separate the "standard UI" features from the "platform-specific" features early and price them differently.
Standard UI - lists, forms, navigation, data display - maps predictably from our React Native experience. Platform-specific requirements - camera, biometrics, push notifications, Bluetooth, deep linking - each carry their own scoping risk because the native bridge work is harder to estimate from the outside.
We also added a device testing budget to every mobile build. Not a line item we charge the client for separately, but time in our project plan explicitly allocated to testing on real devices across iOS and Android versions. Emulators are not enough. We should have known that before we started, but now it is in the process.
What Is Next
We are now comfortable taking on React Native builds for clients who need cross-platform mobile delivery. The first project always carries more uncertainty than you want, and we absorbed that uncertainty so future projects do not have to.
If you have a mobile application brief, we are happy to talk through whether the platform choice and approach fit what you are trying to build.