Download as pdf or txt
Download as pdf or txt
You are on page 1of 16

5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

Uber Blog Sign up

Engineering
Mobile

Building a Scalable and Reliable Map Interface for


Drivers
January 10, 2019 / Global

This article is the sixth in a series covering how Uber’s mobile engineering team developed the
newest version of our driver app, codenamed Carbon, a core component of our ridesharing
business. Among other new features, the app lets our population of over three million driver-
partners find fares, get directions, and track their earnings. We began designing the new app
in conjunction with feedback from our driver-partners in 2017, and began rolling it out for
production in September 2018.

When Uber decided on a full rewrite of our driver app, we used it as an opportunity to step back
and take a look at how we could improve some of the app’s core technologies. One technology
we focused on was the map display, which had become difficult to work with and was
implicated in many bugs that drivers had encountered.

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 1/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

The map display of our previous driver app suffered from the typical problems of legacy
software, designed with one purpose in mind and acquiring new features over time. Those
features occasionally conflicted in their control over the map display, resulting in a subpar
experience for users.

Carbon, our driver app rewrite, gave us an opportunity to re-architect the map, and we came
up with a framework that is both easier to work with and more reliable than the previous
implementation. Setting the map as a background layer and building three distinct
components to regulate the display proved an elegant solution that can scale for growth and
let us seamlessly implement new features, enabling us to better serve our driver-partners.

Legacy conflicts

When we first launched our previous driver app in 2013, its architecture was much simpler. For
instance, our map’s UI only displayed the driver’s location along with the pickup or dropoff
location. Given this limited feature set, it made sense to have a single class manage the map; in
our case, it was called MapViewController.

Figure 1: The driver app map display in


2013 simply showed the driver location
https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 2/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

along with pickup and dropoff points.

Over time, we added many new map-related features to the app, such as in-app navigation, a
gas station finder, rider location sharing, and driver destination filtering. Each of these features
added additional code to MapViewController, introducing complexity which made refining it or
adding even more features difficult.

In 2015, we attempted to simplify the code by splitting its logic into two new view controllers:
OffTripMapViewController and OnTripMapViewController. This division helped somewhat, but
it also introduced new issues. For example, to avoid having the app recreate the map every
time a driver went on or off-trip, we passed the map view back and forth between the two view
controllers. Unfortunately, sometimes the timing was off, and we would send the map to the
wrong view controller. Whenever this happened, the map would totally disappear, as shown in
Figure 2, below, causing confusion for drivers:

Figure 2: Occasionally, the map view


disappeared in our previous driver app
because it was moved to the wrong
view controller.

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 3/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

Another issue occurred when some features needed to exist both on and off-trip. For example,
in-app navigation has long allowed drivers to navigate while on-trip, but we also wanted
drivers to be able to navigate to preferred destinations while off-trip. We ended up duplicating
a large amount of navigation code so that it would work in both view controllers. This problem
illustrated a fundamental issue with the architecture: access to the map was segmented by
app state, but features that needed to use the map had lifespans that didn’t fit neatly into
these segments.

Figure 3: The map architecture in the previous version of the driver app
required duplication of some feature code and gave the view controllers many
responsibilities.

The new map architecture

For the new driver app, we came up with a brand new map architecture to prevent these types
of bugs from appearing. We made the map a background view that persists throughout the
lifetime of the app so that we no longer need to move it from one controller to another.

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 4/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

Figure 4: With our new map architecture, feature code no longer needs to be
duplicated and is distributed in bite-sized pieces.

Three components provide access to the map: the Layer Manager, the Padding Provider, and
the Camera Director. These objects are made available to any part of the code, so map features
can be written in a decentralized manner. With the new app, there is no longer a need to put
map logic in dedicated view controllers, and each map-related feature can have its own
lifespan.

The Layer Manager: Building sandboxes for map elements

Driver app features often want to draw elements on the map, such as markers or overlays. In
the previous version of the driver app, feature code drew elements by calling functions on the
map view itself. This approach worked fine as long as the code was perfectly well-behaved, but
in practice that wasn’t always the case. Sometimes features inadvertently forgot to remove
their map markers, leaving stray elements on the map that persisted until the app was
relaunched, an example of which can be seen in Figure 5. Other times, features removed
others’ elements because they wanted a blank canvas for the map; the other feature had no
idea that its elements had been removed, and behaved as if the driver could still see these
elements on the map.

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 5/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

Figure 5: Sometimes in the previous


app, a feature might leave elements
such as gas station markers on the
map while the driver had commenced a
trip, creating unnecessary visual clutter.

Implemented in our new driver app, the Layer Manager solves these issues by providing a
sandbox for each map feature. Instead of adding elements directly to the map view, features
create their own map layers and register them with the Layer Manager. The map layer’s
interface allows engineers to add and remove map elements, but does not provide access to
any other layers. Features can no longer interfere with map elements that they do not own.

In some cases though, a feature really does require that the the map be cleared of all other
elements. For example, when a driver accepts a new ride request, we want the dispatching
screen to only show map elements relevant to the proposed pick-up to keep the UI clean and
easy-to-follow. In these cases, features can ask the Layer Manager to register an exclusive map
layer. This temporarily hides all other map layers; once the exclusive layer is unregistered, the
other layers can return.

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 6/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

The Layer Manager also has a built-in safeguard to protect against features forgetting to clean
up their elements. In Uber’s RIBs architecture, business logic typically lives in Interactor
classes. When registering a map layer, it is mandatory to provide an interactor with which to
bind the lifespan of the layer so that when an interactor is deactivated the map layer is
automatically removed along with all of its elements. Stray map elements are much less likely
to persist since they are unable to live longer than the feature that created them.

The basic interface of the Layer Manager keeps map layers segregated so they cannot conflict
with each other, as depicted below:

class MapLayerManager {
func add(layer: MapLayer, interactorScope: InteractorScope,
exclusive: Bool)
func remove(layer: MapLayer)
}

class MapLayer {
func add(marker: MapMarker)
func remove(marker: MapMarker)

func add(polygon: MapPolygon)


func remove(polygon: MapPolygon)

func add(polyline: MapPolyline)


func remove(polyline: MapPolyline)

func add(tileOverlay: MapTileOverlay)


func remove(tileOverlay: MapTileOverlay)
}

The Padding Provider: Handling app chrome on top of the map

Having the map as a background view means that app chrome, in other words, elements such
as panels and buttons, will often cover it. The map needs to account for the chrome in our app
so that it can show the driver the streets and locations they need. It would be unfortunate if, for
example, the pick-up pin or the driver’s current location were obscured by an opaque panel.

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 7/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

Figure 6: The bottom panel contributes


map padding to ensure the driver’s full
route is visible.

Since there is no built-in way for the app to know that the map is obscured, we created the
Padding Provider as a component that tracks the visible portion of the map. Any chrome that
partially covers the map registers themselves with the Padding Provider as a padding source.
These chrome elements let the provider know how much they extend (relative to the edges of
the screen) into the map’s bounds. The Padding Provider aggregates all of its sources and
produces an observable stream of the overall edge insets. Any code that wants to show a
certain map region to the user uses this stream to ensure the desired region will be visible.

As with with map layers, each map padding source needs to be bound to the lifespan of the
feature that created it. However, instead of binding to an Interactor (which contains business
logic), padding sources are bound to the view controller so when the view disappears its map
padding will automatically disappear too.
https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 8/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

The Padding Provider allows padding sources to be registered, controlling which portions of
the map can be covered and facilitating an improved navigation experience for drivers. Its
basic interface is depicted below:

class MapPaddingProvider {
var edgeInsetsStream: Observable<EdgeInsets> { get }

func add(paddingSource: MapPaddingSource,


viewControllerScope: ViewControllerScope)
func remove(paddingSource: MapPaddingSource)
}

class MapPaddingSource {
var edgeInsets: EdgeInsets { get set }
}

The Camera Director: encouraging cooperation between features

The map camera is a term that describes the point in 3D space, a vantage point, that is above
and looks down at the map. It is analogous to a film camera, where the map is the scene being
filmed and the user is looking through the camera lens. Features often change the map
camera to show a specific region to the user, such as when a driver is dispatched and we want
to display the pick-up location.

In the previous version of the driver app, any feature could alter the camera’s vantage point by
directly changing its properties on the map view. Sometimes features would want to
continuously update the camera, such as during in-app navigation where it follows the user’s
location as they drive. The problem with this approach is that features were generally unaware
of any other features that may be controlling the camera at the same time. In this case, if two
features tried to control the map camera at the same time, the map would jump back and forth
between two regions, a disorienting experience for the driver.

The new driver app solves these issues with the Camera Director. Instead of letting features
freely control the map camera, the Camera Director provides a way for them to influence the
camera by registering a camera rule. A camera rule has an interface for providing
latitude/longitude coordinates that should be included on screen. Most features do not need
to display a specific region, but may be programmed to ensure that their markers or other
elements are being show to the user. The Camera Director aggregates these rules to come up
with an overall map camera that includes all of the desired coordinates.

This rules-based approach allows many features to function in cooperative manner, a better
outcome than the previous approach by which the camera’s vantage point would jump from
location to location and no guarantees could be made around what the user would see.

However, sometimes the rules-based approach is insufficient, since it does not allow for any
camera control beyond expanding the visible map region. For example, when tapping on a map
https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 9/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

marker that shows an area of high rider demand, we want to display an inspection sheet
showing a route to that area, as depicted in Figure 7, below. In this situation, the map UI is
placed in a temporary mode where it focuses on a particular marker, and we do not want it to
include unrelated locations in the visible map region. For these types of cases, we can request
exclusive camera control from the Camera Director. This provides an object that lets us set a
specific map camera, but only one feature can have exclusive access at a time and features are
notified when they lose camera control.

Figure 7: As we don’t want any other


features to interfere when helping a
driver navigate to a high demand area,
we give this feature exclusive camera
control.

Like the Layer Manager (and similar to the Padding Provider), the Camera Director requires a
RIBs Interactor to bind to whenever a feature registers a new rule or requests exclusive camera
access. In this way, features can only affect the map camera while they are active.
https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 10/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

The basic interface of the Camera Director lets features set their camera requirements, where
the requirements are defined using rules and/or exclusive access, as depicted below:

class MapCameraDirector {
func add(cameraRule: MapCameraRule, interactorScope: InteractorScope)
func remove(cameraRule: MapCameraRule)

func requestCameraControl(interactorScope: InteractorScope) ->


MapCameraHandle
func relinquishCameraControl(handle: MapCameraHandle)
}

class MapCameraRule {
var boundingLocations: [LocationCoordinate2D] { get set }
}

class MapCameraHandle {
var isActiveStream: Observable<Bool> { get }
func set(camera: MapCamera, duration: TimeInterval)
}

Moving forward

In the new driver app, we took away the ability for individual features to directly control the
map, which can lead to conflicts and stray visual elements, as we observed in the past. With
our new framework, features access the map more safely through the mediation of the Layer
Manager, Padding Provider, and Camera Director. We also took advantage of the RIBs
architecture, which requires engineers to write features in such a way, through the use of
interactor and view controller binding, that they do not leave visual elements on the map when
not in use.

Providing these guardrails has resulted in a more robust and scalable map architecture.
Additionally, development of map features has been more effortless, with less need to add
workarounds for other features. Ultimately, the most important benefit is to our driver-
partners– with the new driver app, they will have a more reliable experience picking up and
dropping off riders.

Index of articles in Uber driver app series

1. Why We Decided to Rewrite Uber’s Driver App


2. Architecting Uber’s New Driver App in RIBs
3. How Uber’s New Driver App Overcomes Network Lag
4. Scaling Cash Payments in Uber Eats

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 11/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

5. How to Ship an App Rewrite Without Risking Your Entire Business


6. Building a Scalable and Reliable Map Interface for Drivers
7. Engineering Uber Beacon: Matching Riders and Drivers in 24-bit RGB Colors
8. Architecting a Safe, Scalable, and Server-Driven Platform for Driver Preferences
9. Building a Real-time Earnings Tracker into Uber’s New Driver App
10. Activity/Service as a Dependency: Rethinking Android Architecture in Uber’s New Driver
App

Interested in developing mobile applications used by millions of people every day? Consider
joining our team as an Android or iOS developer!

Chris Haugli
Chris Haugli is an iOS engineer on the Driver Navigation Experience
team. He joined Uber in 2012 and has worked on a variety of projects
since then, but has in recent years been focusing on maps and
navigation in the driver app.

Posted by Chris Haugli


Category: Mobile

Related articles

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 12/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

Engineering, Backend, Mobile

Stopping Uber Fraudsters Through Risk Challenges

January 25 / Global

Engineering, Backend, Mobile, Web

Use Passkeys Wherever You Sign in to Uber

October 26, 2023 / Global

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 13/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

Engineering Mobile

Engineering, Mobile

Measuring Performance for iOS Apps at Uber Scale

April 20, 2023 / Global

Engineering, Mobile

How the Uber Membership Team Developed the ActionCard Design


Pattern to Do More with Less

February 2, 2023 / Global

Most popular
Engineering, Backend February 29 / Global
Network IDS Ruleset Management with Aristotle v2

Engineering, Backend March 7 / Global


Load Balancing: Handling Heterogeneous Hardware

Engineering, Data / ML March 14 / Global


Balancing HDFS DataNodes in the Uber DataLake

Engineering, Data / ML March 21 / Global


Model Excellence Scores: A Framework for Enhancing the Quality of Machine Learning
Systems at Scale

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 14/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

View more stories

Uber

Visit Help Center

Company

About us

Our offerings

Newsroom

Investors

Blog

Careers

AI

Gift cards

Products

Ride

Drive

Deliver

Eat

Uber for Business

Uber Freight

Global citizenship

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 15/16
5/6/24, 8:36 AM Building a Scalable and Reliable Map Interface for Drivers | Uber Blog

Safety

Diversity and Inclusion

Sustainability

Travel

Airports

Cities

English

San Francisco Bay Area

© 2024 Uber Technologies Inc.

Privacy Accessibility Terms

https://www.uber.com/en-VN/blog/building-a-scalable-and-reliable-map-interface-for-drivers/ 16/16

You might also like