At my new job, I wanted to continue working on mobile frameworks and joined the React Native team. Before moving full time on React Native Open Source, I took a slight detour and worked on the company's flagship app, parts of which are written in React Native (RN) . This blog post is a brief account of my experiences with using RN in a large scale iOS/Android app.
Brownfield and Green Field Apps
The main app has many years worth of code in it, spanning multiple teams over many thousand commits. The features are built using an array of internal and open sourced frameworks. Moving the entire app to one framework would be a herculean task for an app of this scale. The teams pick the technology for specific features based on technical merit, developer workflow and team skill-set. Thus, RN is used alongside the other frameworks and UI components, APIs and developer tools are also shared widely.This is what is typically called a “brownfield” app in the community, as opposed to “greenfield” apps that are bootstrapped using RN from scratch. Internally however, this distinction is fluid since all apps are viewed as iOS or Android apps, using preexisting engineering infrastructure to various degrees.
Developer workflow
Most developers using React Native to build features, use Nuclide with its excellent Flow support. Nuclide is also pretty well integrated with technologies across the stack and has support for debugging and inspecting elements. It also proves useful occasionally when using the iOS debugger for native code.I have also found myself dabbling in IntelliJ and XCode when developing/bridging native components or tracking down bugs that span languages. To produce simple test cases for reproducing bugs, we also have a standalone “kitchen sink” app with all components.
One of the biggest differences between the open source setup and our workflow was the use of BUCK as a build system; primarily proving useful due to its cached artifacts in a big project, reducing the overall build times. Most common tasks like adding new screens, generating tests, etc. are automated.
Native Components and Modules
To the end user, the entire app is one cohesive experience and parts of the app using RN should be no different from the rest of the app. The entire app has well defined user interface guidelines and CSS based styling in RN makes its implementation easy. Bridging native UI components for controls that exist is preferred as it promotes style consistency and is such controls are also well tested. We do not use react-native link, but instead rely on the BUCK files to manage JS and native dependencies separately, adding dependencies directly into the native code.Flow definitions are used to ensure correctness between the JS API and the Java/ObjC implementations. This is vital for over the air updates as it ensures that JavaScript does not call into API methods that do no exist. Many other companies using React Native also seem to have similar tooling, and it would be awesome to see some of these tools open sourced.
Navigation
Our iOS and Android apps have their own navigation system to push view controllers or start activities using Intents. The RN navigation module plugins into this system and exposes API methods that make switching between RN and native screens seamless.- Each “route” declares a unique name and defines parts of the navigation UI like the title bar, action buttons, etc.
- This information is then used to generate a JSON file at build time.
- The RN navigation module uses this file to open the correct screen without having to wait for JavaScript to load or show a “redirection” screen.
Loading Screens
As you may have deduced from above, the JavaScript VM may not load until a screen needs it. The navigation also does not wait for React Native screen to be ready before pushing it into the viewport. As a result, there may be a momentary blank screen before JavaScript can kick in to show loading shimmers.To mitigate this, I experimented with adding a loading screen in native code, possibly generated at build time from RN routes. The two issues that I noticed include
- Duplicating A/B testing logic that happens at runtime to ensure that the loading screens have the same appearance as the fully-loaded screens.
- Adding more native code during bridge startup also seems to delay RN start. This is only a problem when a network fetch may be faster than the time to start up the bridge.
Systrace also comes in very handy when trying to understand the performance semantics of such optimizations.
Data Fetch
Most of the RN data fetches happen over Relay. However, there are also cases where we would need to re-use data that are already fetched by native screens. Having a single data fetch strategy would be ideal but for now, we just have a one-off data pre-fetcher so that the network portion of the critical path could be handled earlier. This native pre-fetcher also lends itself into interesting cases where we could predictively fetch data or even load the RN bridge if we believe that there is a high possibility for the user to request it.Conclusion
While I have tried to cover some of the areas of RN I touched in the main app, there are many more areas like A/B testing, gesture handling, etc that I could not go over. Overall, I had a good exposure to complex React Native hybrid app development in the last six months. I learnt that it is important to embrace the platform and build for it.The React Native team has expertise in iOS, Android and JavaScript, and were very helpful in my journey. Coming in as a Web Developer turned Program Manager, I now find myself now working with iOS and Android native code. I realize the importance of continuous learning and stepping outside my comfort zone to learn from other platforms !
I look forward to my next set of projects where I am hoping to help the React Native community.