I arrived fashionably late to the React Native party in Artsy. I had been a part of our Auctions Team, where we worked in Swift with some light-FRP. We were not affected by the 4 months of simultaneous work on moving to React Native, at all.

It was a quiet revolution. I did not have to install npm, I made zero changes to the code for auctions and the whole app's infrastructure barely changed. Yet we moved to making all new code inside our 3 year old iOS app use React Native. What gives?

Well, first up we weren't planning a re-write, we don't have that kind of luxury and the scope of our app is too big compared to the team working on it. Second, we reused existing dependency infrastructure to support JavaScript based apps. Read on to find out what that looks like.

Why we were in a good position to do this

Let's talk a little about the Artsy flagship app, Eigen. It's an app that aimed to comprehensively cover the art world. From Shows to Galleries, Fairs to Auctions, Museums to Magazines.

It all looks a bit like this:

 
 

Our app neatly splits into two areas of view controllers, ones that act as a browser chrome, and individual view controllers that normally map 1:1 to routes on the Artsy website.

For example, the route artsy.net/artwork/glenn-brown-suffer-well maps to the native ARArtworkViewController.

 
 

Just as a browser knows very little about the individual content of the pages that it's rendering, the eigen chrome exists relatively independent of the view controllers that are showing.

Each view controller also knows very little about each-other, so actions that trigger a new view controller are generally done by creating a string route and passing it through the routing system. I've wrote about this pattern in Cocoa Architecture: Router Pattern.

Interestingly, if the router cannot route a view controller, it will pass through to a web view. This is why we consider the app a hybrid app. This pattern means adding new view controllers is extremely easy.

Introducing Emission

Emission is what we use to contain all of our React Native components. Our flagship app Eigen, can depend on and use without needing to bother with the implementation details of React Native. At it's core, Emission is:

  • A node module.
  • A CocoaPod.
  • An iOS App.

The Node Module

Emission itself, is a node module. In our case, it is a JavaScript library that exposes 3 JavaScript objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* @flow */
'use strict';

import Containers from './lib/containers';
import Components from './lib/components';
import Routes from './lib/relay/routes';

import './lib/relay/config';
import './lib/app_registry';

export default {
  Containers,
  Components,
  Routes,
};

Another node project can have Emission as a dependency - then can access our Containers, Components and Routes. A container is a Relay container, a component is a React Component and a Route is a Relay Route.

The thing that's interesting from the integration side, is that each Container is effectively a View Controller that Emission provides to a host application. React Native ignores the concept of view controllers from the Cocoa world, so we have an ARComponentViewController which is subclassed for each exposed Component class.

The iOS App

The iOS app acts as a host target for the CocoaPod, and provides an instance of an AREmission object to the view controllers using React Native. The app is nothing special, it is the default app that is created using pod lib create. We then use CocoaPods to bring in React from inside the node_modules/ folder the Emission node module creates.

The AREmission instance is the intermediary between the host-app (The Emission Example app, or Eigen.) It has an API for handling routing, and passing authentication credentials into the React Native world.

We use the example app to do development inside React Native. As of right now, it is simply a tableview that provides a list of view controllers that represent an exposed Container. Once you are in the right view controller, you can rely on Hot Reloading to simplify your work.

The Pod

An important part of working with React Native, is that you can choose to use native code when appropriate. The Pod for Emission, created entirely in Objective-C, provides:

  • Communication between React Native and the host app objects via native modules.
  • UIViewController subclasses for Host apps to consume.
  • Bridges for existing native views (like our SwitchView) into React Native.

The choice of Objective-C is for simplicity, and language stability. Swift is technically an option, but it's not worth the complications for a few simple objects.

In order to share native views with our host app, Eigen, we created a library to just hold the shared UI components, Extraction. These are factored out of Eigen, and into a pod. Emission and Eigen have this as a dependency.

Pod Deployment

What makes this work well, from the perspective of Eigen is that the React Native comes in atomically. The Podspec references the few native classes, and a single JavaScript file.

This JavaScript file is the bundled version of all our React Native code. It's updated by running npm run bundle. This generates both the minified JS, and a source map so that we can transcribe the error reports into the code we write.

Using the CocoaPod, Emission can provide native view controllers that use React Native under the hood. The host app does not need to know the underlying details like npm.

On Emission

Whether this is a pattern other apps can follow is hard to say, we were in a great position to do this. Our app has view controllers that have very little communication with each other and the host app does not need to bridge large amounts of information.

As ever, our work is open source, and we ensure that anyone can download and run Emission, so if you'd like to understand more, clone artsy/emission and study the implementation.

Categories: eigen, mobile, node, reactnative, tooling

Part of a series: React Native at Artsy