A new beginning for React Native at WalmartLabs Online Grocery
Almost two years ago we launched our first React Native (RN) page. Now close to 90% of our Walmart Grocery App is powered by RN. The majority of our Online Grocery customers use our mobile iOS and Android apps for their weekly purchases so migrating these apps to React Native was a vision that took some convincing and time to achieve. Of course, the process had its share of bumps in the road. This is the story about this journey.
Our Approach and Advantages of React Native
In the end, we chose a hybrid approach where the existing native code still played a big part. We also took in key learnings from other companies’ successes and failures when they were trying to migrate to the technology. As a result of moving to RN we:
- Doubled the development velocity
- Enabled sharing a codebase between iOS and Android
- Drastically improved unit code testability, coverage, and unified automation framework *
- Helped transform our developers to general mobile developers who can build UI in RN as well as iOS and Android
- Enabled flexibility in using Web developers for mobile feature work and vice versa
- Improved the developer experience via instant page reloads.
- Shared business logic with our React/Redux Web App when possible
- Began using Over the Air (OTA) Code Push for critical issues
*We aim for 100% test coverage for both unit tests and integration tests.
How it all started
At Walmart Online Grocery we have been using React for almost 4 years, so it was natural for us to look at React Native as a potential candidate to use in our mobile apps. Our director at the time was a big believer in React Native and he pushed for doing our first proof of concept for RN at Online Grocery. It should be noted that Walmart.com mobile app already had several pages in RN at the time when we started this new journey in January 2018. We talked to their teams to understand their pain points to make sure we don’t repeat them.
When we started we already had well established Android and iOS mobile apps. The initiative was primarily driven by the React/Web part of the organization with crucial help from our lead Android Engineer and the Electrode Native platform team. In the beginning, we only had 2 engineers working with RN at Grocery. Today, we have ~50 mobile engineers contributing to our numerous RN projects. Our RN team includes Web, iOS, and Android developers, so the winds and minds have surely changed.
First Proof of Concept
To start our first POC, we focused on a simple page to build the trust we needed with the rest of the organization. Several pages were considered, including a universal login page which was deemed too complex as a starting point. We ended up picking the last page in our checkout experience, the Order Confirmation Page, or as we call it, the Thank You Page.
It took 1 week to develop the UI in RN supporting the differences in our iOS and Android UI. The transition from working with React to RN was quick. We added analytics, accessibility, and testing in a span of approximately 3 weeks. The real work started when integrating native code and RN code, which has always been time-consuming. To address that, we developed an RN platform called “Matterhorn”, but more on that later.
The Electrode Native Platform is a fully open-sourced project with the sole purpose of facilitating the integration of RN and native apps (also known as a hybrid app). EN targets companies that already have established native apps and primarily want to add new features in RN. I want to stress EN is not RN; it’s a platform to support RN integrations :-). The EN Platform Team initially helped us with 3 important aspects of the development process (more was added later, and I will come back to this):
- EN Container Generation: EN has a concept of MiniApps, and a MiniApp is one RN App (with one or more pages). The EN platform’s container generator creates an AAR file for Android and a Framework for iOS for any number of discrete MiniApps
- EN Cauldron: A Cauldron is a centralized document database that is used to store information about mobile application versions, native dependencies, and information about MiniApps
We have been working very closely with our EN Platform team as the integration aspect has grown in size.
As mentioned earlier, building the actual RN UI was the easy part. It is quite similar to developing pure React. Some of the business logic and Redux selectors were reused from our web app, as both our React Native MiniApps and web app use Redux for state management.
The complexity of this initial project lay in transforming from a web developer into a mobile developer. It is a challenge running both Android Studio and Xcode in parallel, while also running Webstorm, two simulators (sorry Android, I meant emulator), and simultaneously debugging using react-native-debugger, Android Studios logcat, and Xcode Debugger.
During this first POC, we developed some of the building blocks that still help us today. We didn’t need any advanced navigation in the Order Confirmation Page as the only place you could navigate to was the Home Page using the header. We decided to keep the header native and use the inbuilt native navigation for this and inflate the RN view as a Fragment in an Android Activity and as a child ViewController in iOS.
We did have an edge case when we had a network API error where the user could go to either Home Page or Order Details Page from an Error Page, and we chose to create an EN API to facilitate this navigation process.
Continuing our design choice of using a hybrid approach, using the best from both the native and the RN world, we also created EN APIs for business analytics logging and for Splunk engineering logging. Both APIs tie into the same SDKs that the native apps are using.
Before launching any new features or services we always do A/B testing (using WalmartLabs Expo A/B framework). In this case, we (and the product team) wanted to ensure the launch had no negative customer impact. We started A/B testing on Android and launched 100% as all benchmarks looked good after 2 weeks of testing.
Apart from business analytics benchmarks, we also created our own engineering Splunk dashboard monitoring logs like page-load-time, api-response-time, error-conditions, and UI render issues caught in ErrorBoundery wrapper components. We used Crashlytics to monitor crash issues and all the data looked good.
What happened after the POC?
The first POC had been a brownfield project to test the feasibility and advantages of React Native pages as part of our native iOS and Android apps. The project was a success, and the POC was followed quickly by 2 greenfield projects for post-transaction apps, a Grocery Delivery Location Tracker and a Grocery Delivery Experience Feedback app.
The integration work for the LocationTracker and the Feedback pages were both pretty straight forward. Some props were injected in the root component from the native side: order ID, polling frequency, and customer email. Otherwise, the RN app utilized data from their own API calls to our Node orchestration layer. Both apps were also their own standalone React Native MiniApp, and we expanded our EN container build to 3 MiniApps.
A MiniApp is instanced with some boilerplate code that comes with the EN platform and is imported as a dependency to the native apps. Below is an example of the code for iOS.
The BIG migration, and the rise of the Grocery RN Platform
In early June 2018, due to the success of the 3 launched MiniApps, it was decided that we should migrate all native views to React Native, as we saw many advantages during the three projects we already completed.
The internal project was named Matterhorn after the famous Swiss mountain, as we knew it would be hard to reach the summit.
Of course, we received a very short timeline (big surprise, it took a lot longer than expected). After some negotiations, we decided to start with the Discovery portion of the app, which may be the most critical part of our pre-transaction flow. We identified the parts of UI that we would do in RN and the part of UI would remain native. We also identified RN components that would be re-usable in many pages.
The project started as a pure brownfield project; the goal was to re-engineer the native view in RN. To be honest, it was really a skunkworks project for quite a long time. We got 6 new, young, and eager engineers each with some prior RN experience. They became our Dream RN UI team. They started on a Tuesday in mid-June, and the following Monday they were cranking out the new RN UI extremely fast.
While our RN UI team was moving rapidly developing the UI, we focused on the more architectural and design decisions. We knew we had to focus on the foundation for the hybrid approach to work. We also knew that other companies had a lot of issues with navigation and state synchronization when developing a hybrid RN and native app. Our decision was to focus on:
- Designing a new EN navigation architecture
- Implement a uniform way of handling state synchronization between the native model and RN Redux state
We figured hashing out how this should work would take a few weeks (it took longer), and we didn’t want to affect the velocity of our RN UI team. With that in mind, we decided to:
- Temporarily use react-navigation for development, so we could continue to do a weekly demo for the rest of the Grocery team and have a realistic way to test all the flows.
- Run the new RN app standalone for this initial phase using our mock server, as integration to actual Android and iOS would take a while and the navigation and state synchronization needed to be solved first
We used discrete MiniApps for all our previous pages since the EN platform ecosystem is very good at maintaining and making the developer process smooth. In those projects, that made total sense (maybe with the exception of Order Confirmation Page) as they where new standalone features that didn’t share state, UI or APIs with any other part of the app.
For this Migration project, we decided to keep all the RN pages in one MiniApp. We came up with what we thought would be the best approach from many aspects:
- Use a common Redux store
- Same network API’s are used in many different pages, all pages need session/bootstrap data for example
- DRY the code and minimize bundle size as we could do extensive component and business logic reuse
- Have only one place to handle all state synchronizations between the RN and native apps
- Would work very well with our plans to use new dynamic EN navigation architecture
Our New EN Navigation Architecture
In the RN world, the most likely choice would be to use the react-navigation library. We decided early on, in close cooperation with our EN team, that our navigation architecture should remain native and that we should use a new approach based on ideas from our EN team. This approach gave us some real advantages.
- No need for RN to maintain its own back stack. Leverage all that native gives for managing the stack
- Better performance in page transition, native animations and keep the native feel across the app
- A/B testing will become much easier as we could replace a single native page with RN page to benchmark and make sure we didn’t have negative business metric impacts
- Fail-safe to be able to turn off RN pages during migration (what could possibly go wrong)
- Made deep linking much easier to individual RN pages
- Some initial issues with page view analytics events, but this was later solved
- Requires some initial extra native integration work
In our new EN navigation architecture, an RN MiniApp exports/registers many pages, not a single root component in the native apps. Each RN page in our Grocery RN Platform is wrapped in an EN navigation higher-order component. The Navigation component automatically registers the pages/modules in the native apps.
From a native app point of view, Home Page, Cart Page, Search Page, Item Details Pages, etc, are all exposed as discrete independent registered RN MiniApps in the native apps. The example below shows how we (in the latest iteration of EN navigation) register the different RN pages in the native apps. This replaces the regular AppRegistry.registerComponent(…).
When we have a page that requires navigation either to other RN pages or to a native page, we extend the regular React component with a navigation extended component. In this case, we’re navigating to another RN page in the same MiniApp but, it’s the same to navigate to another Mini App in the EN container. Navigation to a native page would look exactly the same as well. The EN Navigation uses the native navigation stack.
The EN Navigationframework also allows us to keep the navigation bar on the native side and manage it completely from the RN component, which is very helpful when adding RN Pages/components inside an already existing native app.
As we decided to migrate page by page instead of all at once and as we used the hybrid approach, we needed to synchronize a number of user interactions and the native model state with Redux. We identified the intersection points that we needed for the apps to work seamlessly. All synchronization needed to work on both iOS and Android naturally.
It should be noted even when we’re 100% done with our migration, it will still be a hybrid RN and native app. We will have all pages in RN, but many core functionalities will still be in native. When we are 100% finished, which is soon (we’re at 90% now), some of the synchronization described below will not be needed anymore. But we will keep out hybrid architecture intact to allow native and RN to contribute equally towards the app development.
Initial Bootstrap and configurations
- Read feature flags from native app to enable turning on and off features for developers, used in VQA, and most importantly in dynamic A/B testing using our WalmartLabs Expo framework
- Read Environment settings from a native app, for example, Production, Staging, Mock server environments
- Read other device-specific data from native apps
In this case, we decided to use the ElectrodeBridge API which exports the Android and iOS Prefered Settings as constants.
- Cart state, cart changes on RN page should update the native model with RN Redux cart state and vice versa
- Favorite item state and favorite item changes on the RN page should update the native model with RN Redux favorite state and vice versa
For any add/delete to cart, favorite/unfavorite change made in the native app pages, a general EN Notification API was created. As before, the API was self-generated based on a Swagger schema we defined.
We do favorites sync from native similar to cart below.
Synchronizing the RN cart changes to native apps turned out to be quite a bit more difficult. We’re using a very complex Cart Finite State Machine (FSM) Middleware in RN to safely take care of a myriad of cart uses cases. We lifted all this business logic from our React/Redux Web app, which saved us a lot of time. We then created a new EN API that we use exclusively for cart and Favorites RN changes, as the general notification API was not sufficient in this instance.
Other Type Synchronization Scenarios
Honestly, not all this came to us from day one, but they now are all handled by the same handle-native-event.js that we use for all native events. It’s also powered by the same general EN Notification we created. Examples of some use cases handled by this pattern:
- Sign/Sign Out. RN needs to know to update session state
- Store changes and delivery address changes
- Business logic state changes in native pages not yet migrated
- 3rd party SDK notifications that should change call to action components on certain pages
- Page change events to be used for analytics
It should be noted that the same EN APIs also work the other way and are used for this as well as in other use cases, e.g., RN event emitter >> native event listener && RN event listeners and vice versa.
How We Launched the Grocery RN Platform Pages
After numerous failed attempts at passing A/B tests with all our initial Grocery RN Platform pages (we had way too many pages in one test and so many business analytics benchmarks to pass), we realized that the strategy was to do them one by one instead. This worked out really well. As I mentioned before, our navigation architecture makes it very easy for us to do this gradual rollout.
Workflow and Native Releases and the EN CI for React Native
Today we have a fully automated CI at WalmartLabs that includes building EN containers for all the MiniApps.
- Time triggered daily builds are created and kick off the EN container build from all the MiniApps master branches. When a container is built, AAR or Framework, the dependency is updated automatically to the development branch of the Android and iOS apps.
- At regular CodeFreeze day, native CI is triggered which cuts a native Release/7.10.0 branch, for example, thereafter it kicks off the EN CI that cut’s corresponding Release/7.10.0 on all our 8 MiniApps. A new release container version 7100.0.0, in this case, is then generated and integrated to the native releases branches.
- Integration tests are run after any new container is updated in a native app. We use Magellan/Nightwatch/Appium/Saucelabs for this and our mock server of course.
We’re currently using Fabric but are moving to a new tool that uses source maps. To facilitate the process of creating source maps, we got a new tool from our EN team that auto-creates them for every container build. This makes the triage of RN related crashes much easier.
What We Did to Make it Easier for Developers
Early on we decided to create a detailed GitHub Wiki for everything related to how to get started with RN and our EN support platform. All processes, build flows, setups, PR processes, and best practices were continuously updated. The philosophy was to add any question asked multiple times to the Wiki right away and provide a link as an answer. This saved us a bunch of time when onboarding new developers. And we are always looking to automate more processes as we did with the CI.
Let’s face it, React Native is very polarizing for engineers. It was no different for us, and we initially didn’t have that many native engineers believing this was a viable path forward. Things have changed though, after a year we transformed from a small team of 8 developers to over 50 developers contributing to our RN repos. What changed to make this happen?
- Make it very clear in interviewing new native developers that they will be Mobile Engineers and will be cross-trained in React Native. The goal should be to be able to contribute to all 3 platforms. This is the same goal for RN developers coming from the JS world
- Adding a mix of native and RN engineers to each new feature project. This will lead to cross-training plus ownership
Where Are We Today, and What Does the Future Hold for RN at Grocery?
We’re moving full speed ahead and are currently involved in new projects that use RN front, back, and center. Our goal is to have all pages in RN very soon and we plan on migrating some of the MiniApps that are not part of the Grocery RN Platform back into it. We constantly keep up to date with the latest RN version and work with performance enhancements. We also will create more NativeModule UI components for existing complex native UI where it makes sense.
Other WalmartLabs React Native Medium Articles
Electrode Native Navigation
Today’s mobile-technology climate brings myriad development platforms upon which applications can be built, each with…
React Native at WalmartLabs
Here at Walmart, the customer is always #1, so we’re constantly on the lookout for ways we can improve upon the…
Electrode Native: The Platform For Integrating React Native Into Your Apps
Developing mobile applications that are fast, bug free, and fully featured, while building upon advancing mobile…
This article was written as a collaborative effort by the engineers at WalmartLabs Online Grocery mobile team — Anders Bengtsson and Daver Muzaffer.
Special thanks to Saneesh Chulliparambil, rockstar lead Android developer at Online Grocery, Electrode Native guru Deepu Ganaparhiyadan who was instrumental in working with us on the integration. He is also the creator of the EN Navigation library. Thanks to the father of the EN Platform Benoit Lemaire one of the best engineers we encountered Further thanks to Krunal Shah, for all the support he has given us and all the late-night he spent Code Pushing to millions of users with us. Special thanks to our young RN dream team that helped kick off this project. Finally special thanks to Alex Grigoryan our VP of Engineering and to Arpan Nanavati for both pushing for the RN for Online Grocery and believing it was the way forward.