Recently, I had the pleasure to experiment with maps and API integration in React Native while working on the iOS app for uncovercity.com.

The challenge can be summarised very easily:

  1. a car (ordered via a ride-sharing app like Uber) picks you up at a specific location and takes you to a surprise dinner at an unknown venue.
  2. when your dinner is over, you can use the app to order a ride home.

What made the challenge interesting was that we were forced to test the ride-sharing part of the app in production from the start, so we were constantly in touch with drivers and sitting in fancy cars with a phone in our hands and a computer on our laps.

The challenge

To make the experience as pleasant and predictable as possible for the user, we implemented a live map showing the current position of the vehicle when it’s underway, for both the pickup and the return journey.

In the screenshot above, you can see the different states of the app:

  • pickup journey: when the vehicle is on its way to pick up the user at home
  • searching vehicle: after the user requested the return journey
  • return journey: the vehicle on its way to pick up the user at the restaurant

In this post, I’m going to talk about how we did this and what challenges we faced.

The solution

The technical solution involves three main components:

  • Redux to update the vehicle position on the map every time we get a response from the API
  • Redux Thunk actions that dispatch asynchronous API requests to trigger a response from the server (due to limitations, this had to be done via polling)
  • an API proxy that would communicate with the ride-sharing service and filter/map the data according to our needs.
  • Expo’s MapView component (which is based on the React Community’s React Native component) to show a marker with the current coordinates of the vehicle.

Working around an unfinished API

The API proxy sounds like a weird thing to do, but we actually had to put that layer in between our app and the ride-sharing API for several reasons. There were cases when no vehicles would be available, and we couldn’t rely on the app itself to send another request (because it could be inactive). Instead, a cron job handled that task.

Unfortunately, at the time we built the app there was also no way to use the ride-sharing API’s websocket integration which would push status updates to the app. So we had to go for good ol’ polling and send requests to the server every few seconds kto obtain the current location data. Since we didn’t expect there to be tons of requests per hour, this seemed a viable solution for the beginning, even though not a very scalable one.

High-level overview of the app interacting with the ride-sharing service's API via Redux

Things got more complicated when we found out that the ride-sharing service’s testing environment was still in development. So any possible scenario (ride ordered, car on the road, car arrived, all drivers busy, etc…) we needed to respond to in our own product could only be tested in production! More on that later…

Displaying markers with Expo’s MapView component

The experience of working with Expo’s MapView to display live markers on the map is pretty straightforward.

<MapView
  ref={ref => {
    this.map = ref;
  }}
  provider="google"
  initialRegion=
  customMapStyle={STYLES.MAP}
  onLayout={() => this.onMapLayoutSuccess()}
>
  {this.props.location && (
    <MapView.Marker
      identifier="vehiclePosition"
      coordinate=
      image={ICON_MAP}
    />
  )}

  {this.props.status === "DRIVER_ONROAD" && (
    <MapView.Marker
      identifier="restaurantPosition"
      coordinate=
      image={ICON_RESTAURANT}
    />
  )}

  {this.props.status === "CLIENT_ONROAD" && (
    <MapView.Marker
      identifier="homePosition"
      coordinate=
      image={ICON_HOME}
    />
  )}
</MapView>

Updating coordinates is also really simple: A JavaScript interval fires a redux-thunk event to dispatch a new API request every second. The response then gets processed by a redux reducer and the data sent to the view. Once the MapView Marker receives the new coordinates, it updates its position instantly.

Fitting the map to a set of supplied markers

The MapView component also makes it easy to focus the map on a set of markers and animate it smoothly to show the corresponding area of the map with every update, using its fitToSuppliedMarkers method. Unfortunately though, the method doesn’t allow for any padding (unlike its fitToCoordinates counterpart) to make sure the markers don’t overlap with other overlaying elements on the screen.

Luckily, the community provides a solution that’s very easy to implement:

fitToSuppliedMarkersCustom(coordinates) {
  const markers = [];
  markers.push(this.vehicleCoordinates());
  markers.push(coordinates);

  this.map.fitToCoordinates(markers, {
    edgePadding: {
      bottom: 200, right: 50, top: 150, left: 50,
    },
    animated: true,
  });
}

All you have to do is store the markers’ reference using React’s ref attribute, push them to an array and use the fitToCoordinates method instead.

Testing our scrappy solution

As you can imagine, the first few attempts of properly syncing the API responses with the UI were pretty unsuccessful. That’s not a big deal when you use a testing environment and your requests don’t have any impact on the real world. Our case though was that every time we “tested” the API, we’d request a real vehicle.

What worked in our favour though was that the ride-sharing service provides a 30-second courtesy time of cancelling a ride, not the most ideal feature for drivers, but at least it would allow us to adjust and fine-tune our system, with the minor downside of sending drivers to random destinations all the time.

I actually ended up in the car of a driver who complained about our request/cancel approach of bulletproofing their service’s API. But despite making some drivers really mad at us and even though we didn’t develop the most elegant solution (using polling, testing in production, relying on cron jobs), it actually works solidly now, and we managed to bootstrap our way through limited resources and unexpected constraints to a production-ready app.