Investigating React Native
On the Twitch mobile team, we care a lot about making sure we use the best tools available to build our apps. React Native is a topic that came up a lot internally, but unfortunately none of us on the team really understood the pros and cons of the framework.
With the blessing of my team, I spent a week using, reading about, and testing React Native on Android. I built a fairly simple Twitch client in React Native, hooked in custom native components that aren’t provided directly by React Native, and then investigated the costs of integrating React Native into a pre-existing, fully native app.
How does React Native work?
First, I think there is a lot of misconceptions on what React Native actually is. It specifically is not a glorified webview. React Native works by spinning up a Javascript thread that interprets Javascript code, communicating with native components under the hood. For example:
This is Javascript code that is interpreted and converted into an Android TextView. Likewise, If you create a button in React Native, it internally creates an Android view. When the Android view is clicked, it forwards the click from native code to Javascript.
What I liked
React as a paradigm for front-end development
React’s approach to writing UI feels great to use. Instead of manipulating views with imperative setters, UI is simply a function of state in React. Whenever you modify the internal state of a React component, React triggers a render() call onto the component. This is an incredibly simple approach to UI development that feels great to use. It makes it very easy to reason about your UI — it’s all right there, clearly declared in a render() function.
Live reload
One of the coolest features about React Native development is live reloading. As soon as you save a Javascript file, React repackages the Javascript bundle and ships it to the device, displaying the change immediately without recompiling the apk. The iteration loop in React Native is insanely fast compared to native. Building UI in Android is oftentimes a nonstop loop of trial and error, tweaking values ever so slightly. Instead of waiting 10+ seconds every single time for your apk to recompile, changes appear immediately.
Learn once, write everywhere
After spending a week writing React Native, I strongly believe that “Learn once, write everywhere” holds true. Once you learn the principles for building a React app, you basically can jump into any React code base. I spent some amount of time reading over our React mobile web codebase, and feel comfortable in saying I understand it and could contribute to it today. Similarly, I bet I could easily contribute to an iOS React Native codebase today.
On reusability, it seems fairly trivial to share logic between React code bases. While you sometimes need to dive into Android or iOS native code or write Android/iOS specific modules, the majority of Javascript code should be easily shared.
What I disliked
Cryptic errors
Error messages from React Native were often very confusing. I can’t count the number of times I would modify a file and run the app on my phone only to see some cryptic error message. Errors would appear with stack traces that go through React Native internals, giving me no insight into what I actually broke. For example: importing a custom module with a typo gave me this error. No guidance other than to check my outermost render method.
Immature platform
React Native feels like an immature platform. While React Native provides many components out of the box, a lot was missing. I could not find (excluding third party libraries) built-in ways to show checkboxes, spinners, video players, etc. Many tools we use at Twitch, such as Fabric for crash reporting, did not have official React Native support.
On top of this, things just don’t feel stable. The docs reference the Navigation component for navigating screens, but I later learned after implementing it that this was basically deprecated in favor of react-navigation! Finally, while I certainly did not experience this in my one week using the platform, I read many concerns of React Native’s release to release stability.
A big thing to realize is that React Native isn’t just an SDK or a library you are adding. It’s an entire platform on which you build your app. You are very heavily tied to React Native and very reliant Facebook’s continued support and development of React Native. For a platform that feels immature, this is a hard pill to swallow.
Android as a second class citizen
React Native was built for iOS first, and it shows. The documentation has many statements like: “This section will be updated shortly showing an integration into a more real world application such as the 2048 app that was used for Objective-C and Swift.” As an Android developer, this was disheartening.
The Multiple Repos problem
In order to truly reap the benefits of code sharing between Android/iOS with React Native, the two apps need to share a codebase. This is only a problem if you want to integrate into pre-existing Android and iOS apps, but merging an Android and iOS codebases isn’t exactly an easy feat. At Twitch, because our native Android and iOS apps have separate teams and processes, this would completely disturb how we handle continuous integration, code review, tagging release, etc. There are ways to get around this though. This tech talk explains how Airbnb encountered and hacked around the problem.
Performance
One big worry my team had about React Native is the performance cost. React Native runs on a single Javascript thread, and if it ever gets backed up your UI will suffer as a result. While writing my simple test app, I didn’t really run into performance issues on my Pixel. Unfortunately, when I tried to use some lower end devices, I could never get React Native to actually run. All of my pre-5.0 devices would fail to launch with a “Could not get BatchedBridge” error (see: cryptic errors). I spent some time googling and trying various solutions, but none of them worked for me.
Still though, just reading online about performance is a bit worrying. One common theme I’ve found is that for performance heavy work, companies are having to shunt the work to native. See [here](https://medium.com/@talkol/performance-limitations-of-react-native-and-how-to-overcome-them-947630d7f440#.ez9q112oz) and here. For an app like Twitch (live playback, fast chat, inline playback within content directories), the performance considerations are very important. I particularly worry about missing multithreading and true memory management. On custom native components, it was actually pretty easy to create one myself (the documentation is pretty sparse though). This means though that in order to get the most out of React Native, you probably will need to be proficient in native development to begin with, which seems to circumvent some of the value proposition of the platform to begin with.
Javascript
It’s Javascript. Losing type safety, compile time errors, having to deal with the insanity of this binding, etc was really painful for a week. I know that I personally started mobile development specifically to avoid Javascript. If you have a team of native developers already, I worry that it would hard to convince them to all want to learn use Javascript all over again. Of course, you could mitigate these concerns with something like Flow or Typescript (our mobile web team uses Typescript).
Integration into an existing app
Finally, one of the big questions I had was how hard would it be to integrate pieces of React Native into our pre-existing Twitch app? You should be able to have a React Native activity intermixed with standard activities. Here are some of the issues I ran into:
React Native only ships 32 bit binaries. There has been an open issue for this since 2015. In practice, that means all libraries that have 64 bit binaries have to be stripped of their 64 bit binaries because you cannot use a mix of 32 and 64 bit binaries.
React Native is dependent on deprecated tools. For example, you have to set android.useDeprecatedNdk=true in order to use React Native.
Following the guide for how to integrate React Native into an existing app did not work for me! Getting the Twitch app to build with a custom React Native Activity was an exercise in frustration. Even after sorting out the above two issues, I kept getting “BatchedBridge” errors. I googled and tried various solutions fruitlessly.
This is certainly the road less traveled. It was far harder to look up information online about integrating React Native into an existing app versus a brand new one.
I eventually admitted defeat and gave up trying to integrate React Native into the Twitch app with my limited time. Instead, I opted to create a test Android app and integrate React Native into that. This was far easier to do and at least gave me confidence I could eventually add React Native to the Twitch app if given enough time.
One important open question though is that, out of the box, React Native is designed to only be used within Activities. And while fragments are a contentious topic in the Android community, there is no documentation for how to integrate React Native with fragments (there is an open PR for this).
Finally, I worry about the costs of sharing state between native code and React Native. You can create native modules to do so, but this adds yet another layer of code that developers have to worry about maintaining.
Conclusion
If I were to make an app from scratch that needed to be cross platform with little performance concerns, I’d very likely choose to use React Native. My cons list was larger than my pros list, but I really cannot emphasize enough how great the React model felt to use. My biggest concerns though are that consistently, it feels like React Native isn’t quite there yet.
We decided that React Native isn’t something we wanted to integrate into the Twitch mobile apps today. React Native is at its strongest when you start an app from scratch with it. Rewriting our mobile apps from the ground up isn’t something we really want to do, so the only option would be to add pieces of React Native into our apps. And for the various reasons mentioned above, it just doesn’t seem worth it for us to do so. I still remain very excited and hopeful for React Native in the future though, and many other companies have proven you can add React Native to large, pre-existing apps.