Relaunching TaskRabbit's Tasker App on React Native

Realm React Native Launched!

At Facebook’s React.js Conference 2016, we launched a new Realm mobile database built specifically for React Native. It offers easy object persistence and full query capabilities, with a performance profile that’s usually 2–10x faster than existing options.

React Native is a new way to build mobile applications where most of the code is written in Javascript, but with a native experience as the end result. So many things are better in this world: We can share 80% of the code between iOS and Android, the development is 5x faster than traditional means, and we still have the ability to write native code when we need it. TaskRabbit recently re-wrote their Tasker app with React Native, and Brian shares his team’s experience relaunching, explains how the magic works and some of the patterns at work in their Tasker app, and then dives a little deeper into how to use Realm to make it even better.


Introduction (0:00)

My name is Brian Leonard. I am the chief architect and technical co-founder at Task Rabbit. Task Rabbit is a website and an app, actually several apps, to help you get stuff done.

The app I’m going to be talking about today is the Tasker app, that the taskers use to find work, to get work, to chat with the client, to invoice them, to say that something has been paid, and that they’re owed the money. And so we have two apps on two platforms. We re-wrote this app in React Native over the last couple months. Why did we pick that one and not the other one? Because it’s better to try something new in an app that has tens of thousands of people using it than one with millions of people using it like our client-side app. If you’re going to try out some random, new technology, I highly recommend trying it out on the one most people aren’t using.

Prototyping (3:49)

I’ve done Android development and iOS development. When we started prototyping, we found that it was 10X faster to develop in React Native than in native iOS. This was mainly because when you’re developing in iOS, you have to compile it and confirm on the simulator and see how it went. In React Native you don’t have to recompile it every time. Instead, you can hot reload each little change and the whole time between typing the code and seeing the results is cut down almost infinitely. It’s not seconds to compile, it’s half a second to refresh. And then what you end up doing is you end up making smaller changes – every change you make is actually slightly smaller.

If you’re making small changes and then you see what that change looks like immediately and then make another small change, you stay immersed in that environment and you make smaller, more confident changes and there’s a lot of productivity that came from that for us.

Routing (6:55)

The first thing we looked into was routing, and that’s one of the things the sample app has. I really thought about deep links really hard and so every page, every screen in the app, the goal was to be URL-addressable.

For example if you were looking at /ongoing/42/chat, the /42/chat is the page you’re looking at, and the ongoing is the section that’s within. Then suppose you tapped on an icon in the corner to look at your schedule, then that would just add /schedule to that URL, and when you hit the back button it would be popping an item off of this thing. So this thing maps one to one with the route stack: in iOS it would be a navigation controller, and in Android it might be an activity.

This had a lot of benefits. The first is that the URL had to completely contain the state of the app. The whole screen and the whole app had to be able to bootstrap from the URL, and so you can’t assume there’s going to be something in Realm or Core Data or SQLight. But assuming you can do that, you can always jump to another section of the app with no problem. Your push notifications can be super dynamic. We’re actually sending down the URL of where to skip to in the push, and so when they slide it we can twiddle the whole app to be exactly where they want it to be.

If you always knew the current URL and if the whole thing knew how to bootstrap from just that URL, you could store that locally, say in a preference or a document on the hard drive of the phone and when you hit Command + R it could know to immediately refresh that. So now, not only are you not compiling every time and then clicking into that spot in the app where you were, you’re refreshing and you’re staying on the same page and you’re iterating and building up that code more quickly.

Flux (10:55)

There’s this pattern in a lot of React, it’s called Flux. It’s kind of like ReactiveCocoa: something happens and then you dispatch that to let everyone in the world know that thing happened, maybe if you downloaded some new data for example. That goes into a store which then alerts the views, so the views are subscribed to the store.

When you hit a button on the view, it kicks off an action which talks to the server or does whatever you’re doing to dispatch it, which tells the store, which then tells the view to update. And the cool thing about React Native is usually when you see this pattern you’re operating on deltas.

The whole React Native thing is about always rendering everything, every time. The really interesting thing that React does is it diffs it (because it’d be really expensive to re-render the whole UIView hieracrchy every time), so let’s say you had a whole bunch of state and then the user hit a little button and so you changed one little piece of state from four to five, some counter. In your code, you re-render what the whole world should now look like but React will notice that the only difference is this one little spot on the screen, this one little text view within this whole hierarchy should now be five instead of four and change just that. So as the developer, the ergonomics is such that you’re always proceeding very confidently by rendering the whole thing, but the engine is essentially a diff engine that’s only making the smallest possible change to the screen. And all of that is to say how Realm gets used. This is our component, and it’s subscribing to the change listener inside that store:

class Tasklist extends React.Component {
  componentDidMount() {
    TaskListStore.addChangeListener(this.onChange);
    TaskListActions.fetchList();
  }
}

const TaskListActions = {
  fetchList: function(callback) {
    # use API

    TaskListService.fetch(function(error, tasks) {
      if(callback) callback(error);

      if (!error) {
        Dispatcher.dispatch({
          actionType: AppConstants.TASK_LIST_UPDATED,
          tasks: tasks
        });
      }
    });
  }
}

const TaskListStore = Object.assign({}, EventEmitter.prototype, {
  get() { return _immutableArray; }
  emitChange() {this.emit(CHANGE_EVENT, model);},
  addChangeListener(callback) { this.on(CHANGE_EVENT, callback);}
});

Dispatcher.register(function(action) {
  switch(action.actionType) {
    case AppConstants.TASK_LIST_UPDATED:
      setList(action.tasks);
      TaskListStore.emitChange();
      break;
  }
});

class TaskList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { tasks: TaskListStore.get() };
  }

  onChange() {
    this.setState({ tasks: TaskListStore.get() });
  }
}

We fetch some stuff from the server and we say, “Hey guess what, the task list has now been updated.” The store says “Great, emit the change,” which is to say, tell everyone that’s listening that I’ve now changed, and we re-render ourselves.

Data Storage (15:15)

So then the question is, “How do you do that data storage?” and Realm recently released a React Native engine to their C++ Core. There are two kinds of data storage issues that we came up against. One is large lists of things – the tasks that a tasker is currently working on and an especially large list: the histories taskers have done (we have taskers that have done tens of thousands of tasks and they have that on the list), and also the chat messages on each task.

Then the other issue are singletons. The singleton store when done in JavaScript looked like this:

var _singleton = null;

var SingletonStore = Object.assign({}, EventEmitter.prototype, {
    initialize: function() {
      // called when app loads to set _singleton
      // uses keychain and local storage to load it up
    },

    get: function() {
      return _singleton;
    }
});

You might load that up from local storage like the documents folder. You might load that up from the keychain if there was sensitive data or something like that.

The list store you can imagine doing in that same way, in which there’s just this in-memory array of things you would ask for, and when you downloaded new ones you’d push them onto that array:

// null so we know it's initializing on app launch

var _immutableArray = null;

var TaskListStore = Object.assign({}, EventEmitter.prototype, {
  get: function() {
    return _immutableArray;
  }
});

But that’s not too good because you can’t really get a particular task from that so you might end up with this kind of thing in memory with a _hash and then in the _hash contain an array:

// null so we know it's initializing on app launch

var hash = null;

var TaskListStore = Object.assign({}, EventEmitter.prototype, {
  get: function(listName) {
    return _hash[listName];
  }
});

Or you could also have a different store for each list. You could have the active task list and the history list:

var _immutableArray = null;

var ActiveTaskListStore = Object.assign({}, EventEmitter.prototype, {
  get function() {
    return _immutableArray;
  }
});

But then when things move between those two lists, it might get kind of complicated. Could they be in both at the same time? All of this is to say, we had all this code that did all these kinds of things in memory, and we just dropped in a new Realm database that represented those tasks. We kept exactly the same stores, so the views never changed, and now we just fetch the list from the task. When they say, “Give me the active tasks,” we basically do a get on the db where one of the columns is named “Active” and we sort it by when it was last updated and we return that. So one of the big values of Realm that we saw was the ability to drop it in only at the data layer and not change any of the view logic involved.

var db = new Database("tasks");
var fetched = false;

var ActiveTaskListStore = Object.assign({}, EventEmitter.prototype, {
  get: function() {
    var results = db.get({list_name: "active"}, {sort: "updated_at ASC"})

    if (results.length === 0 &&& !fetched) {
      return null; // show loading
    }
    return results;
  }
});

Testing (18:18)

There’s a couple types of testing you want to consider:

  • Unit
  • Component
  • Integration

If you’re only going to do one kind of testing, I highly recommend doing integration testing because that’s the closest to what the user can see. That’s something that’s actually running the simulator, so we did mostly that.

On our real app we have thousands of tests that do this it takes a long time to run, which then we parallelize using fifteen different VM instances to get good coverage. This is using something called Mocha, and Appium, and Koa.

Sharing Code Across Platforms (20:25)

Not everything is shared in React Native. You can write C and/or Java code in each of the platforms. Push notifications is certainly different between the two. The forms are kind of different, the way you pick dates and pick images are different, along with modals. But on the whole that’s only about 13% of our code, so 87% of our code was shared, especially all the business logic and most of the views.

To summarize, React is super awesome, and React Native is doubly-awesome (maybe even ten times as awesome?). I highly recommend you check it out if you’re doing any web development. React Native twice as awesome because you get both of the platforms and a faster development cycle on each. I’m really excited how people are really examining some of the core tenets of mobile development – certainly Realm, certainly Facebook with React Native, and just re-examining how things have to work. I’m seeing lots of people taking a step back and thinking about new ways that they can do things, and we’re ending up in a step-level function of productivity there. In the companies I advise, I tell them that if they’re not at least considering React Native, they’re making a mistake.

Q&A (23:00)

Q: So is there anything that you can’t do with React Native such as having a fully functioning mobile app in Android and iOS that uses the full capability of the hardware, and works offline?

We’re certainly working offline. We’re using Realm to have all that data. It’s really important to us because a tasker needs to always have access to that data even if they’re on the subway in New York City.

The really interesting thing about React Native is that it’s not that game you think you know with Javascript and it’s shabby web views. There’s a bridge between the C or Java code and the JS. And so we’re tracking locations, we have the native camera, we take pictures, we select things from the camera roll, we’re using the keychain to store authentication tokens, all of that stuff.


Brian Leonard

Brian Leonard

Brian leads the engineering teams in building TaskRabbit's applications and infrastructure. He focuses on creating a platform that enables innovation and reliably scales with the growth of the service. The company's technical co-founder, Brian built and launched the TaskRabbit site. In addition to drawing architectural diagrams, he is still coding to help turn learnings into better experiences for Clients and Taskers.