TL;DR we used React Native components inside native views. We enjoyed great success in iOS, not a single crash reported in 6 months. However, the performance on Android was not up to the mark. There was a visible lag, especially on low-end phones.

Horizontal scrolling widget has been built in React Native

We released a horizontal scrolling widget in the Grofers app across all mobile platforms in March’17. It was fully developed in React Native. This article elucidates our journey of successful proto-duction, the framework we set up and the challenges we faced while building it.

In early 2017, we began working on a solution that would help us move faster with making changes on our mobile apps. Being a startup, it’s important that we are able to run quick, multiple iterations for a feature so that we experiment fast and learn fast. We need to perform rapid experiments without committing large resources on a feature. Being a lean team, every tech resource is precious to us.

The idea to use React Native was envisaged for features that could be shipped faster and a product with higher iteration speeds.

React Native helps build a real mobile app that’s indistinguishable from an app built using Objective-C, Swift or Java because it uses the same fundamental UI building blocks as native iOS and Android apps. Development is faster since there are no compile-install cycles.

Integration with App

A brief background on our app — our app’s landing page is completely widgetized, that is, the home page is a composition of stand-alone views that can be reused and presented in any order driven by the backend.

The goal was to develop new widgets in React Native that would work across all mobile platforms.

React Native can be used in following ways:

Entire app in React Native

Integrate with existing apps as stand-alone whole page view

Integrate inside a native view in existing apps

Ours was the third case — integrating React Native view inside a native view. Integrating React Native into an existing native app can create challenges and additional work that you don’t encounter when you start building an app from scratch. And integrating it into an existing view is even more challenging.

We aimed to build a framework in which React Native views could be flexibly used anywhere in the app, including native’s ListView in Android and UICollectionView in iOS, without loss of generality.

The process of using React Native component can be broadly divided into following stages — create a React Native bridge, instantiate React Native view, embed it in native view. We need to make sure that these React Native components are always in sync with native. Each of these processes has its own sophistication which we will discuss as we go along.

Bridging & React Native Views

React Native bridge is the backbone of communication between native and React Native. After observing the performance of bridge instantiation, we decided to create the React Native bridge at the start of the app and keep it alive throughout the app’s lifetime (so that for each module we didn’t have to re-create it). We created a singleton class ReactManager, which composed of React native bridge and few other methods and properties of which we’ll talk about in a while.

After the bridge is set up at app launch, we instantiate the RCTRootView class. It’s a native class that is used to host React-managed views within the app. We instantiate the horizontal scrolling widget with properties such as the heading the products data. These properties come from a backend API. We also add the shopping cart data to the properties so that products in native cart and React Native widget are always in sync.

React Native — Native Interaction

Data sync between React Native and native is a critical aspect.

Making changes to the shopping cart from a React Native widget should reflect changes spontaneously in native and vice-versa. For this, we decided to keep the native data as the source of truth.

Any change in the shopping cart data was notified to the React Native modules, which in turn had listeners set up for the event. For instance, whenever a user added an item from any React Native widget, a native method was called via the bridge which performed the required actions, including adding an item to the native database and triggering a notification with updated item quantities. This event was listened by the other React Native widgets and they updated themselves according to the updated data.

Over the Air Updates

One of the USPs of React Native is that you don’t need to go through the app release hassle. You can deploy updates directly to users’ devices. React Native has bestowed upon us the power to tweak the widgets, iterate through experiments, add new widgets, add analytics tracking as incremental changes without having to send an app update via App Store / Play Store.

First we thought of building an in-house system using cloud storage services. It would work by acting as a central repository to which we can publish updates to, and apps can query for updates from at the start of the app.

For building proof of concept, we decided to use Microsoft’s CodePush, a cloud service that enables React Native developers to achieve the same. It’s still in beta phase. We have been using it for quite some time in production and it has been performing satisfactorily. It has saved our valuable resources from development and maintenance of an in-house system.

OTA Challenges

As we were planning to add new React Native widgets via OTA updates without going through app release hassle, a big challenge we were facing was the unavailability of React Native module. Each of our React Native views is a separate component and it was possible that react bridge is asked to fetch a component or access a module that has not been added to the bundle yet. In such a scenario, the React Native framework crashes the app.

To solve it, we created an Initialiser class with a static method in the JavaScript application which is called just after the bridge is instantiated. Its job is to communicate the modules available in form of app keys in the JavaScript bundle to the native ReactManager class.

Initialiser Class

So now, ReactManager makes sure only those components are accessed whose app keys have been set.

iOS

We checked the app size after adding React Native libraries and increase in size was less than 1 MB.

We supported our app on the iOS 7 platform as well. We moved our app to the minimum supported version of React Native — iOS 8. With iOS 10 already out and declining iOS 7 users, this was a small cost to pay for the greater benefits.

Performance

As part of the widgets developed in React Native, we examined React Native’s impact on several metrics, including crashes and loading time. No crashes due to React Native were reported in iOS. A few thousand crashes were reported in Android, which resonates to less than 1% of all Android users.

As Instagram reported in their blog, we also observed that React Native has a start up overhead cost, which is mostly due to injection of the JavaScript bundle into JavaScriptCore (the VM used by React Native both on iOS and Android) and instantiation of native modules and view managers. This delay in instantiation is sometimes high enough that thee feed screen of the app is rendered without React Native widgets (because app keys have not been set in ReactManager till that moment). However, once the React bridge is ready, we can reload the feed.

Visual lag in rendering React Native views on home page

Loading time of React Native views has been a cause for concern. Loading time is defined as the interval between instantiating a RCTView in native and the callback received in componentDidMount method in React Native component class. Following is the average loading time of first widget on home page on iPhone 6s running iOS 11 and Lenovo K4 Vibe Note running Android Marshmallow:

iOS: 133 ms

Android: 1007 ms

As we can gather from the numbers, there is a visible lag in rendering of React Native views.

We also audited the performance of synchronisation between React Native and native. We recorded the time between a click on a product’s add-to-cart button in horizontal scrolling widget and the event listener callback in React Native application. Following is the average time of 10 samples on same devices:

iOS: 12 ms

Android: 121 ms

Performance is noticeably faster in iOS than Android.

Conclusion

With React Native, we solved most of the problems at hand. We can ship new features with common code for mobile platforms, hence, lesser use of resources. We can develop faster with tools like Live Reload and Hot Reloading. We have the power to iterate over features quickly with over the air updates. And there’s scope of cross-functionality between web and mobile teams.

Our next points of attack would be to optimise the React Native framework, decrease the loading time of React Native modules and the crash rate in Android.