Engineering

Earlier this year, Facebook open sourced React Native. For those unfamiliar with React Native, it is a framework that allows you to write native iOS and Android apps using JSX and Javascript. As a result, developers who are familiar with React.JS can use their existing knowledge to write native mobile apps – all they have to do is write React Native components instead of React.JS components. Facebook meant it to be a learn-once-write-everywhere paradigm as opposed to write-once-run-anywhere. React Native has been evolving really quickly, and has received a lot of acclaim from developers.

We recently decided to give React Native a try, allowing us to re-use a lot of code from our web/desktop clients (Actions, Stores, and basically anything other than components), resulting only in a rewrite of components for Native (iOS and Android).

One of the features we’ve been thinking about since we started this project is sending JS code updates over the wire (and bypassing the AppStore review process for small changes). During our 2015 Thanksgiving Hackathon, we built ReactNativeAutoUpdater to do just that. Let me talk about some ideas that went into it.

Downloading Updates

Technically, we can ship a React Native app with no Javascript code, and point it to a URL on the server on the internet. That would be just like how the dev environment works. The problem with that is that JS-bundles tend to be very big. A simple app can easily run into a couple of megabytes. Wasting the time and bandwidth to download them every time the app runs is clearly not an ideal way of doing things.

Luckily, this is super easy to solve. We can ship the app with an initial JS-bundle, and check for updates in the background after the app starts. To optimize it even further, we can avoid downloading the same file again and again by using HTTP Etag Caching.

But we still end up with the following problems:

  • What happens when we download a JS update which contains a new component, but the native counterpart of that component is not included in the native app on the device? This can happen if the JS update is downloaded before the AppStore update.
  • What if we want to download JS updates only when there are significant changes in functionality?
  • What if we want to decide how frequently the app should check for updates?
  • How do we make sure the app has a certain version of JS code before updating to the latest one? For example, v0.23 can only be downloaded if the app is v0.19+. All other versions have to upgrade to v0.19 first.

To deal with these problems, we decided to go with a meta-file approach. A meta-file lives somewhere on the internet, and contains information about the update (version, minimum container version, url, etc). The app asks for the meta-file, and determines if the update should be downloaded or not.

Here is a sample of the meta-file.

{
    "version": "1.1.0",
    "minContainerVersion": "1.0",
    “url”: {
	”url”: “/s/3klfuwm74sfnj0w/main.jsbundle?raw=1"
	“isRelative”: true
    }
}

The JSON dictionary is simple:

  • version— this is the version of the bundle file (in major.minor.patch format)
  • minContainerVersion— this is the minimum version of the container (native) app that is allowed to download this bundle (this is needed because adding a new React Native component to your app might result into changed native app, hence, going through the AppStore review process)
  • url— this is where ReactNativeAutoUpdater will download the JS bundle from
  • isRelative — this tells if the provided URL is a relative URL (when set to true, you need to set the hostname using the method (void)setHostnameForRelativeDownloadURLs:(NSString*)hostname; )

Usage and Configuration

ReactNativeAutoUpdater is currently available for iOS as a CocoaPod. The library is highly configurable, and the Github Repo page explains the usage in great detail.

You can configure how frequently to check for updates, and what kind of updates should be downloaded (major updates, minor updates, or all). It can be allowed to use cellular data, or only Wi-Fi. The updates can be downloaded in the background silently, or the progress can be shown to the user.

React Native Auto Updater In Action:

React Native Auto Updater in action

What’s Next?

The work we’ve done is by no means complete. These are the things that can be done to improve ReactNativeAutoUpdater.

  • REST Endpoint for Fetching updates— The meta-file approach works, but it is quite primitive. Instead of that, the app can call a REST endpoint, send its current JS-Bundle version, native container (app) version, and the endpoint will point it to the right URL to download the bundle from. This will also make sure the update-dependency is maintained (e.g. update #23 should be installed on #22, but not directly on #20 or below).
  • Delta Updates— Using Google’s Diff-Match-Patch, we can only send the diff in the updates, hence reducing the bandwidth used significantly.
  • Android— A version of the library for Android.

We plan to work on these features as time permits, or on as-needed basis. In the meanwhile, we hope you find our work useful. ReactNativeAutoUpdater is available under the MIT license on GitHub. Any suggestions or pull requests to the repo are most welcome.

Cheers,

— Rahul and the AeroFS Team.