React Native: Is It Worth It? (Part II)

Andy Dyer
ProAndroidDev
Published in
8 min readDec 7, 2017

--

Intro

In Part I, we explored reasons you might want to use React Native and some of the challenges I encountered attempting to integrate it into an existing app. My goal was not to scare you away from it, but rather to demonstrate that it is a large dependency and may limit your ability to upgrade other libraries. In Part II, we’ll examine the development experience, cross platform code sharing, and dynamic updates. We’ll finish with an example showing how you can combine a React Native UI with existing native code to get the best of both worlds.

Developing with React Native

Assuming you still aren’t deterred, let’s talk a bit about what it’s like to actually develop an app with React Native. First things first, you’ll need to pick an editor. While you could certainly use Android Studio to edit JavaScript files, your best bet is to use a separate text editor that provides first class support for them. Both Atom and Microsoft’s VS Code are good choices with a lot of plugins available. I started out using Atom at first and later VS Code along with the Chrome Developer Tools for debugging. Be sure to pick an editor you’re comfortable with, as most of your React Native development will take place there rather than in Android Studio or Xcode.

Coming from Android Studio, most text editors leave a lot to be desired. Import statements must be added manually…like a caveman. Autocomplete is usually just an indexed list of words in open files unless you install some plugins. Most of the quick fix/refactoring tools we’ve come to rely on are typically not available (although VS Code is adding more and more with each update). This also means that syntax errors may not be apparent until you check the console (where your yarn start command is running) or try running your changes on a device. Thankfully, neither of these take anywhere near as long as waiting on a Gradle build to run and deploy to an emulator.

Much has been written about how inferior JavaScript is compared to other languages and yet here we are using it heavily more than 20 years after its initial release. jQuery made it more palatable for a while but has since been supplanted by ES6, which fixes many of its shortcomings. Even so, those of us coming from statically typed languages will likely prefer using Facebook’s Flow or Microsoft’s TypeScript to bring type safety to our React Native apps.

We’ve chosen TypeScript (side note, TypeScript vs Flow is akin to the VIM/emacs religious wars) for its first class support in VS Code. This gives us refactoring, go to definition and a lot of other IDE related features. Here’s a small example of a component using TypeScript:

Getting comfortable with React Native involves a learning curve similar to any large architectural framework like Dagger or language like Kotlin. For many, working with React Native means learning React and Javascript simultaneously.

Not-So-Great-Things

As you can see, developing an app with React Native requires learning quite a few new concepts. In addition, there are a few less-than-ideal things worth mentioning.

First, I was surprised to learn that there is currently no built-in way to save and restore state when handling configuration changes like an orientation change. This forces us to use the android:configChanges="keyboard|keyboardHidden|orientation|screenSize" hack we’ve all learned is not the right way to do this. Or, we could lock the app orientation to portrait. But I’m not sure which is worse.

What’s more, many standard Android things like Material Design widgets and language support require third party libraries. It’s great to see the community fill these needs, but an officially supported solution for some of the most frequently used platform features would be comforting.

When all else fails, interop with native code is possible, but requires some additional classes and configuration to define the interface between React Native and your JVM code.

This brings us to our second consideration:

It’s way different than normal Android development.

If you have a team of React or Javascript developers and need to build a mobile app, this may be perfect. However, if you have a team of longtime Android engineers, React Native may be a tougher sell, especially with things like Kotlin going mainstream and increasingly better tooling.

Don’t Drive Too Fast

As I mentioned in the intro, one of the killer features of React Native is the ability to change code and see it on a device almost instantly. Or, to put a darker spin on it:

https://twitter.com/mxcl/status/883159808643395588

Trying to build too much too quickly with any technology before having a solid grasp of the fundamentals can lead to technical debt. Luckily, there are tons of great tutorials and books available to guide you.

One proven way to ensure quality code is to test as much of it as possible. There are a few frameworks that help us with this on React Native:

  1. Jest — Snapshot & unit testing. React Native apps have this configured out of the box.
  2. DetoxEspresso like UI testing. Also known as end-to-end or gray box testing, Detox runs your tests on an emulator so you can validate what the user actually sees.
  3. Enzyme — Shallow rendering means tests can run a bit faster than Jest with a more flexible API.

Cross Platform

Cross platform code sharing is also routinely mentioned as a benefit of React Native. While we’ve been able to have the same code run on both Android and iOS, things like keyboard behavior and platform specific styles often mean diverging at some point. Best practices for cross-platform code sharing are still evolving. How much to share and what is okay to duplicate are the two toughest questions here. As much possible, breaking functionality down into reusable components and differing only where necessary should give you the flexibility required to keep the app feeling native on each platform.

There are a couple ways to specify platform specific behavior. First, we can use a simple if (Platform.OS === 'android') check in our code, similar to how we conditionally handle OS version differences on Android. But sprinkling code like this throughout your app will make these important platform specific differences difficult to maintain. The second, slightly better option is to include the *.android.js or *.ios.js designation in your file names. By convention, files ending with *.android.js will be loaded by Android and those ending with *.ios.js will be loaded by iOS. If you are able to reduce platform-specific code to small components and minor style differences, this approach should work a bit better.

This brings us to our third consideration when deciding to use React Native:

Clean and maintainable cross-platform code requires the commitment of everyone on the team.

Dynamic Code Updates

Another feature of React Native that is not available to traditional Android apps is dynamic code updates via Microsoft’s Code Push service. With Code Push, you can deploy updated Javascript code to your users without deploying a full app update to Google Play or the Apple App Store. This means bug fixes and new features can get to your users as quickly as possible. While this sounds too good to be true for anyone familiar with Apple’s stringent review process or anyone who’s received one of those scary automated terms violations emails from Google Play, the FAQ on the Code Push site assures us everything’s cool:

According to section 3.3.2 of Apple’s developer agreement, as long as you are using the CodePush service to release bug fixes and improvements/features that maintain the app’s original/presented purpose (i.e. don’t CodePush a calculator into a first-person shooter), then you will be fine, and your users will be happy. In order to provide a tangible example, our team published a (pretty cheesy!) CodePush-ified game to the Google Play Store and Apple App Store, and had no problems getting it through the review process.

So, while the official answer is “you will be fine”, it’s up to you to decide if the fact that they support their claim with a single piece of anecdotal evidence is enough for you to feel comfortable using it for your project.

I’ve only given Code Push a test drive so far, but it was enough for me to be simultaneously impressed and scared of how it could be abused in the wrong hands. The documentation does a good job of explaining best practices for deploying updates without surprising the user. In short, you should ideally wait for the subsequent app session to apply dynamic updates.

This brings us to our fourth and final point to consider before adopting React Native:

Dynamic code updates are a game changer, but should be used responsibly.

A React Native UI with Existing Native Code

Since one of the primary benefits of React Native is how quickly you can see changes on a device, it’s great for rapidly prototyping a new feature. You can use this to get early feedback on an idea before investing the time to fully develop it natively. But what if you have an existing app with API and/or database code you don’t want to reimplement in Javascript?

As I said earlier, React Native allows us to interface with our native app code. Leveraging this, it’s not very difficult to use existing native code to drive a UI developed in React Native.

Here’s an example of how to connect React Native to LiveData, which Google introduced at I/O this year as part of the Architecture Components:

First, on the native side, we create a BeerLiveDataModule that exposes a subscribe() and an unsubscribe() method to React Native. Both of these methods are annotated with @ReactMethod, which indicates that we want to expose them to JavaScript. When subscribe() is called, we start observing our BeerRepository’s LiveData. As you probably guessed, when unsubscribe() is called, we do the opposite. Any time LiveData emits an update, we post an event to Javascript via React’s device event emitter. You could certainly do something similar with RxJava as well.

Once we have our native module ready, we register it in our app’s ReactPackage class, which we created when we integrated React Native into our app.

Then, on the React Native side:

First, we add the corresponding listener and subscribe to our native module’s changes in componentDidMount(). In componentWillUnmount(), we once again do the reverse. Whenever we receive an event from the native module, we simply update the state with the new data and the view is rerendered to display it.

If you only need to asynchronously fetch data on the native side once, you can pass one or more callbacks from Javascript as a parameter to your native module method. Either way, with a bit of effort, you can start benefitting from React Native right away without having to rewrite your entire app.

Conclusion

So, is it worth it? It depends. To recap, here are some things to consider when deciding if React Native is right for you:

  1. It’s a big dependency and may limit your ability to upgrade other libraries.
  2. It’s way different than normal Android development.
  3. Clean and maintainable cross-platform code requires the commitment of everyone on the team.
  4. Dynamic code updates are a game changer, but should be used responsibly.

To be clear, I’m not saying you shouldn’t use React Native. I’m working with it now and (mostly) enjoying it. I’ve also talked to people at several companies who are successfully sharing JavaScript code between their React web apps and React Native mobile apps. And what’s more, it’s clearly gaining traction where tools like PhoneGap, Cordova, and Xamarin have not.

This is the part where I ask you to clappity-clap if you found this useful, etc. Thanks for reading!

Originally published at andydyer.org

--

--