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

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

Andric
10 min readJan 22, 2019

This is the second part of a two-part post. You can read Part 1 here, where I introduce the context and motivation behind our decision to port our standalone native iOS/Android apps to a unified React Native codebase.

“Skate to where the puck is going, not where it has been”

— Walter Gretzky

As a product team, building a resilient and sustainable product that enables tomorrow’s effort is our top concern.

This is why, in trying to solve our development pain points, we didn’t just consider the merits of Javascript development versus native mobile development. We also considered how React Native would fit into long-term trends around product design and product management.

In this section, I’ll dive into each of the criteria with which we evaluated React Native, as well as how React Native solved problems for us along each of these criteria.

🖌 React Native and Product Design

We were particularly interested in how React was emerging as the lingua franca of user interfaces.

Because React Native uses a subset of CSS and Flexbox for styling and layout, it’s trivial for designers to make changes to the layout or styling code. Designers can now directly influence the end-product: instead of designing a picture of the UI, you can design the UI itself.

We also saw how React was heavily embraced by the design community in early 2018. Influential voices in digital design were evangelizing it as a way to build maintainable, modular design systems: Jon Gold (Airbnb), Koen Bok (Framer), Sacha Greif (Sidebar), Max Stoiber (Spectrum), and Sarah Drasner (author of “Web Animations”).

This was incredibly promising, and we wanted to choose a technology (React or otherwise), that would help our designers and developers get around the “design hand off problem”, and work more closely together.

10 months after we made the decision to move to React Native, we’re impressed by how much of this design-dev “singularity” has come true. Design tools are increasingly adopting React-friendly workflows, with React Native support soon to follow.

To touch on just a few highlights, you can now:

This tight integration between design and dev tooling — the promised land, so to speak — was a major motivator for us to develop in React Native. While the promise of a unified design/development tool isn’t yet a reality, it’s not that far off.

Today, we’re able to reap the benefits of tighter designer-developer collaboration with component-based design systems. What makes React so alluring, is how it affords us a unified language to talk about our UI — one of props, state, and context.

🌈 React Native and Product Management

With React Native, we haven’t just seen improvements in the way our designers and developers are able to collaborate. We’ve also been able to deliver value to users much faster, with apps that are higher quality and in less time.

Before React Native, each new feature required us to specify UI interactions, layout, styling, and business logic twice, once for each platform.

This incurred a necessary discovery cost, since most of this “specification” work is done by Product Managers and Product Designers. Instead of spending time on discovery activities like research, prototyping, and experimentation, we ended up spending most of our time on supporting delivery activities.

When evaluating React Native, the principle of continuous delivery was something we looked to achieve.

Agile vs Waterfall (from agilenutshell.com)

Continuous delivery means we’re able to release small batches of code that add value to users, relatively quickly. Such code can be released once you’ve verified that it works properly, instead of coordinating on an exact calendar date to release something big that can fail in all sorts of unpredictable ways.

It’s not hard to see why so many consider continuous delivery to be the holy grail of software development. Designing and developing software is unpredictable, and deploying updates to running software comes with zero marginal cost.

Before React Native, we had to operate in fixed timeboxes, or “sprints”, because we’ve had to manually check for platform-dependent defects and design inconsistencies in order to ascertain that they were good to release. Automated testing required us to write code twice, which didn’t exactly help make things go faster.

Now, with a unified codebase, we’re able to write one set of automated tests, in order to verify that the app is working as intended across both platforms.

Additionally, with the help of Expo and React Native, we’re able to continuously deliver updates to users over-the-air. Users are able to use an updated version of the app without having to download an update from the app store.

Image result for expo over the air updates
How over-the-air updates work with Expo (source: Expo blog)

Automated testing, paired with over-the-air updates, lets us ship higher-quality code that we’re more confident of, in a shorter time. This is a marked improvement over the 2-week release schedule we’ve had to stick to in order to ensure high-quality releases.

For us, this is more than a nice-to-have. With a great diversity of smartphones that are in use today, we can’t always predict what device or OS-specific issues may arise. As a financial institution, being able to respond quickly in fixing such issues as they arise, helps us maintain a reliable service users can trust.

💻 React Native and Software Engineering

Finally, in evaluating React Native as a technology choice, we looked at the core principles behind its design, and considered what it meant to our particular business context.

Here, we look at three principles that define React Native:

  1. “Learn once, write anywhere”
  2. Components as a unit of reusability
  3. Uni-directional data flow

“Learn once, write anywhere”, or, cross-platform UI abstraction

The fundamental theorem of software engineering states:

We can solve any problem by introducing an extra level of indirection, except for the problem of too many levels of indirection

With React Native, we’re able to move the abstraction layer that describes how our app should look and work from the “specification layer” to the “implementation layer”.

In other words, instead of writing a doc to describe how our app should behave, we can move it to code, where it can be tested.

Formalizing this abstraction allows us to avoid expensive inconsistencies between platforms. We avoid many inconsistencies except for specific UI touches that are adapted to each platform’s design language.

With React Native, this is something that is trivial to achieve, given that it exposes most of the underlying mobile APIs in a unified way — a <Switch /> component renders an Android switch on Android phones, and an iOS switch on iPhones.

In cases where this is not provided by either React Native or Expo, we were able to easily adapt how our app looks based on each platform.

Here’s how this approach was introduced the the developer community when React Native was first announced in 2015:

It’s worth noting that we’re not chasing “write once, run anywhere.” Different platforms have different looks, feels, and capabilities, and as such, we should still be developing discrete apps for each platform, but the same set of engineers should be able to build applications for whatever platform they choose, without needing to learn a fundamentally different set of technologies for each. We call this approach “learn once, write anywhere.”

— Sophie Alpert, Introducing React Native

What it meant for us, was that we’re able to build an iOS an Android app with the same underlying core UX and business logic, while adapting certain pieces of UI to the operating system’s design language.

Because this is already how our app is spec’ed out during the design phase — our iOS and Android app share the same user flows, and are not thought of as discrete apps — we thought that the way React Native deals with cross-platform UI was a great fit, since it’ll only save us time.

Components as a unit of reusability

Reusability is one of the key selling points of React Native, often mentioned in the same breath as “design systems” and “development speed”.

React helps us break down a graphical user interface into discrete, reusable components (source: “Thinking in React” from the official React docs)

React’s component architecture clearly supports design systems, and provides a natural way of thinking about how to compose complex screens from smaller units, but what of the promise of development speed?

We considered how the myriad of ways a well-maintained design system / component library would speed us up:

Speed of iteration between development cycles

  • Reusability of components between different features
  • Reusability of business logic between platforms
  • CSS means designers can contribute to design revision without handoff or handholding

Speed of issue resolution for production bugs

  • Ability to ship faster without compiling a new build

Speed of onboarding web developers and designers

  • Greater cross-functionality and ownership
  • Developers that work across the stack on a single solution are more empowered to solve the underlying customer problem
  • Designers that can contribute to how their design is implemented are more likely to push for design touches that can improve product quality

Uni-directional data flow

Coupled with React’s component architecture, dealing with React’s uni-directional data flow has made dealing with complex UIs much less of a headache.

Source: Flux (Facebook)

While the surface area of the TenX Wallet app seems small at first glance (as a user, you’re likely to only interact with a few screens at once), the app itself is chock full of stateful logic; such as a user’s country of residence, their eligibility to order a card, the state of their card order, whether they’re verified, their preferred display currency, their wallet balances, and so forth.

We especially like React because data flows in one way from parent to child, making it easy to reason about. Combined with a functional coding style, data flows in a React component can be pure and deterministic, and therefore tested independently of each other to be bug-free.

⚖️ Trade-Offs

While we determined very early on that React Native was a good choice for our particular context, since React Native is a relatively new technology (it’s not yet v1.0), we expected to run into some technical issues.

Some of the known issues with React Native are performance issues with lists and animation, especially on Android, which we were fortunately able to workaround in a few places. In practice, while performance was slightly worse-off than pure native in these places — resulting in a few dropped frames — we didn’t find them to be significantly worse.

For us, the pros mostly outweigh the cons, since our app is scant in long lists or intricate animations.

In the long run, we expect many of these performance issues to be solved in the coming months, as community efforts shape up around react-native-screens, react-native-gesture-handler, react-native-reanimated, and, in the further future, React Native’s upgraded Fabric architecture.

Why we decided to go with React Native despite these trade-offs

Most of our UI views are fairly simple.

We take some data from the server and render a view based on that. React’s asynchronous rendering model excels in that, even if it struggles with buttery-smooth gesture handling and animation.

We don’t need to squeeze out every last ounce of performance.

We’re not using GPS, accelerometer, 3D graphics, long complex lists, or anything that would require raw performance.

Conclusion

React is 🔥 on fire 🔥 these days. Developers and designers alike have lots to be excited about, finally being able to speak a common language when referring to user interfaces.

After a long debate about whether designers should code, the answer is now a resounding yes — React is now the the de-facto abstraction for screen design, having now become a first-class citizen in next-generation design tools like Framer X, UXPin, and Modulz.

Of course, the core React library is itself chock-full of new features coming down the pipe that makes building great UIs way simpler.

For those of us building mobile apps, it’s disheartening that the mobile dev ecosystem trails behind web development by quite a lot.

Fortunately, React Native is moving fast. Facebook just announced a large-scale rearchitecture of RN’s internals, on the way to launching 1.0. Expo also recently launched the first of their “Expo for Professionals” initiative, which greatly improves the experience of developing mobile apps.

Adopting React Native sends your mobile development workflow into the not-so-distant future. But it’s not without downsides.

There’s still a long list of trade-offs that you can’t ignore (albeit a much shorter list than the benefits you’ll get, depending on your situation).

As software engineering doesn’t happen in a vacuum, I’d caution any team that’s evaluating React Native, or an alternative like Flutter, to consider your particular business context, especially as it pertains to product design and product management.

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. ✨