Embracing React Native for Lean Mobile Development (Part 1 of 2)

A look into how and why we migrated the TenX Wallet apps to React Native

Andric
7 min readJan 22, 2019

Disclaimer: I’m no longer working at TenX. This post is the original article I wrote and published on the official company blog, while I was an employee. Since the company decided to move their blog from Medium to a self-hosted site (the moved version is here), this article is now published under my personal account.

In 2018, my team at TenX decided to rewrite our flagship app, TenX Wallet, from standalone native iOS/Android apps to a unified React Native codebase.

Launched two years prior, the Wallet app went through multiple design iterations, and accumulated significant technical debt.

In this two-part post, I want to get into why and how we decided to take a bet on React Native and the Javascript ecosystem 10 months ago, and how it’s paying off for us.

In the second part of this story, we’ll dive into the criteria we used to evaluate this decision, which will hopefully help teams in a similar situation decide the best mobile development approach for them.

🌟 Background and motivation

About one year ago, the Wallet team started to get into quite a tricky spot.

Support tickets were piling up. Our backlog of bugs was growing faster than we could chew through it. With every new feature we built, new bugs emerged.

Logging into JIRA felt like jumping off a cliff into the deep end of development hell

Each new feature shipped felt like we were moving one step forward and two steps back.

With each release, we were no longer delivering significant value, but found ourselves reacting to issues as they came in. We were fixing them as quickly as we could, but never quickly enough. We were running sprints, but it didn’t feel agile at all.

Saddled with the burden of maintaining two codebases and developing the same features twice, we constantly ran into platform-specific bugs that we had to resolve, and business logic inconsistencies that led to us laboriously specifying desired behavior up front.

If we continued working like this, I thought, we’d be running headlong into a wall of UX and technical debt.

This led me to ask:

How might we deliver more user value with less effort?

Specifically, we wanted to find out if we could:

  1. Write more cross-platform code, to avoid repeating our efforts twice in design, QA, and dev
  2. Write more modular code that’s easier to reuse, so that we can quickly experiment and iterate
  3. Write more predictable code that’s easier to test and debug, so that we can release faster

At the same time, we’ve already been reaping these benefits with React on the web, having built our internal tooling, website, and (now-deprecated) webapp with it.

That prompted us to seriously consider React Native as a solution for the problems we faced with mobile development as a small team.

We knew that React is a different beast on mobile than it is on the web, so this required a deeper look.

💼 Business Context

No one technology choice can change the way that you work, but the right technology, aligned with the size of your team, can enable you to work in a way that’s more suited to what you’re trying to build.

Before we go any further, it’s important to note that blog posts about technology choices are contextual. What works for one company will not work for the next. Case studies from Airbnb, Discord, and Pinterest show just how much your mileage will vary in adopting a new technology such as React Native.

At TenX, we have a small frontend engineering footprint (most of our business logic lives on the server side), and a product that’s in the early stages of its lifecycle.

We’re also a small team. There are five of us building and maintaining the TenX Wallet app. Our team consisted of a single software engineer, a product manager, a QA engineer, and two product designers working across iOS and Android.

That meant we had to bias our product development process towards cross-functional collaboration, speed, and flexibility. Specifically, we needed to adopt a “lean” approach to development, which let us:

  • Treat code as the source of truth. Designers, product managers, and developers collaborate closely on building the product. Everyone can make pull requests against a single codebase to modify how it looks or how it works.
  • Focus on learning, not pixel-pushing. Designers can spend time doing user research and prototyping to discover what our users want, rather than spending time pixel-pushing in Sketch.
  • Test and release often. Write automated tests, so that you can continually release new features with the assurance that it won’t fail in production. Maintain a modular, reusable design system so that you can quickly mock up prototypes to test with real users.

Ultimately, when considering our particular business context, we realized that native mobile development is antithetical to agile software development, and did not work for a team our size:

  • Code cannot be the source of truth, since native code is split across two codebases. Some specification is needed to align the two.
  • Similarly, Designers and PMs spend time specifying designs and desired behavior, to ensure consistency between platforms. It’s also not realistic for Designers to contribute code, as styling/layout is significantly more challenging in Swift and Java than it is with the CSS Box Model.
  • Tests have to be implemented twice: regression tests, acceptance tests, unit tests. While we’re able to leverage Gherkin and Cucumber to write our acceptance criteria once, this also falls into the nebulous territory of “over-specification”. Additionally, App Store releases require Apple approval, which limits how often we’re able to release.

With this knowledge, we decided to go ahead with the decision to sunset our native iOS and Android apps, and port them over to React Native.

This decision wasn’t made lightly, given that we’ve maintained separate native mobile apps for iOS and Android for over a year, a conscious decision at the time because we valued a well-designed user experience.

At the time, the kind of high-quality UIs that we desired was only possible with pure native apps. React Native wasn’t stable enough on Android for us to consider, and high quality libraries like Expo, React Navigation, styled-components, and react-native-gesture-handler weren’t released yet.

Fortunately, React Native, along with the Javascript and Typescript ecosystems, have matured a lot in the past year.

🐣 Migration Strategy

Once we decided to make the switch, there was still the question of how best to adopt the technology.

Together with a senior frontend engineer, I looked at how other companies adopted React Native into their development workflow. Ultimately, we decided to rewrite our apps from scratch (a “greenfield” app), instead of taking the brownfield approach of bridging React views over to our existing native apps.

Our reasons for going with a “greenfield” approach were:

  • The size of our team: We have a small team maintaining the mobile app.
  • Availability of technical knowledge: More of us in the company know how to write idiomatic, modern Javascript than Swift, Objective-C, or Java. We already write Javascript in production, with node.js on the backend, and React DOM on the frontend.
  • An app with a relatively small footprint: A large proportion of our business logic lives on the backend.
  • Technical debt: Our existing iOS and Android apps were accreting design and technical debt that fixing them up to play nice with React Native would be much harder than starting from scratch.

We also considered the difficulty of integrating React Native into a brownfield app, as opposed to starting from scratch with a greenfield app.

As mentioned by Airbnb in their post “Sunsetting React Native”, fundamental conflicts in development culture between iOS/Android/Javascript ecosystems — incompatible tooling, idioms, and design patterns — can make a brownfield setup difficult to sustain in the long term.

Finally, the developer experience of maintaining a brownfield app is less well-supported, not to mention the current stability of the React Native JS bridge. Since these are unresolved issues with the underlying technology, we decided it was best to go with a greenfield approach.

💡 What we learned

One of the things we realized early on as a product team is how reality and expectations don’t often align when it comes to technology choices.

Most crucially, we learned that the “default” choice of how to do something — in our case, native iOS and Android development—might not be the best for our particular context.

Similarly, libraries, tools, and frameworks marketed as alternatives to development teams (such as React Native and Expo) can look better than they actually perform in reality.

Sometimes, the best decision is to do nothing and stick to the status quo. It only makes sense to consider alternatives when the “default choice” no longer works.

Chasing “trending” technologies can get you into trouble quickly, since trends change. In the time since we decided to rewrite our apps in React Native, Google‘s Flutter has emerged as an alternative.

React Native is trendy right now, and so is Flutter. Search trends are an important signal in deciding what works for you, but trends come and go — so take caution!

If we had evaluated our decision by looking at the hottest framework, we would not have benefitted from the rewrite as much as we have.

The lesson is this: to decide on an alternative, the specific nuances of how your team works matters more than what’s trendy right now!

This post is Part 1 of a two-parter story, and continues here.

Part 2 of this post will dive into the nuances of how we evaluated React Native as a technology, by considering the various implications of changing your technology stack: how it impacts product design, product management, QA, and the larger engineering org.

Thanks for reading! If you liked it, clap for this article using the button below.

--

--

Andric

Creative Kind. I live for the everyday magic and moments of serendipity. ✨