This article is the seventh 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.
Pick-ups are a particularly difficult pain point for both riders and drivers due to both technical and physical factors. On the technical side, we need the rider and driver apps to be appropriately synchronized. In physical space, such as a crowded street at night, it can be challenging for the rider and driver to find each other. Helping riders easily identify their driver is a key to a successful and enjoyable trip on our platform.
Uber Beacon represents a natural extension of our apps into the physical world, a device that provides intuitive color-matching between a rider and driver, where the rider can choose any color in allowable the spectrum. Something as intuitive as picking a color and seeing it light up your driver’s windshield on a dark and stormy night can make a big difference even though it seems simple. And in fact, the engineering that drives that experience is anything but simple. Let’s take a deeper look at how we leveraged our latest driver app to bring that experience to life.
Most of the mobile engineering for Beacon is on our driver app. Implementation uses two sets of resources: the Beacon SDK, responsible for communicating with Beacon over Bluetooth LE, and a series of Worker objects in the app’s RIBs architecture that utilize the Beacon SDK to drive functionality.
Underpinning Beacon’s entire user experience is the Beacon SDK, which handles Bluetooth LE connectivity and communication between the driver app and Beacon. In designing the Beacon SDK we adhered to three tenets:
- Platform agnostic
- Application independent
- Reactive interfaces
Because Uber’s mobile architecture is platform agnostic, supporting drivers with both iOS and Android phones, it was important that the Beacon SDK architectural design be platform agnostic as well. By allowing engineers to speak in terms that are cross platform, we were able to move faster and provide easier integration for other teams that needed to leverage Beacon.
A key tenet of SDK development is to ensure the SDK is not dependent on the consuming application. This was crucial for us as we gradually rolled out the new driver app. Keeping the SDK independent of the consumer allowed us to iterate on the SDK and provide improvements to both new and old driver apps in production to help avoid disruption for drivers.
Uber adopted reactive programming across its mobile apps, so it was important to design the Beacon SDK to expose Observables that adhere to the Observable Contract. Engineers using the SDK can focus on interacting with Beacon in a reactive manner. The intricacies of low level Bluetooth APIs are abstracted away.
We designed the Beacon SDK to expose a series of manager objects where each manager is responsible for a subset of Beacon’s functionality, such as connectivity, LED control, Over-the-Air (OTA) updates, and sensors. Each set of managers is responsible for interoperability with a single Beacon, and the driver app can operate any number of Beacons by instantiating a set of managers for each device.
Each manager is defined by interface and the SDK provides implementations of these interfaces. These managers provide the reactive bridge to the imperative platform-level Bluetooth APIs, which will allow commands to pass from the driver’s phone to a Beacon.
Since Beacon doesn’t have a dedicated user interface, almost all Beacon functionality is provided through Workers. Workers are essentially interactors that have no dedicated UI component, so the logic is not tied to interaction with views in the app. Carbon attaches workers at the lowest RIB required for a given functionality, and the Workers are detached and made available for garbage collection as the RIB is removed from the RIB tree.
Each of these Workers is dependent on one or more Beacon SDK managers. The managers are provided by the closest parenting RIB shared by these workers. When a worker is instantiated, it is injected with managers provided by the upstream scope. In this way, Beacon managers are shared across all workers, and the workers are not responsible for the lifecycle of these managers.
The RIBs tree allow us to enable and disable the plugin points for these workers depending on whether or not a driver is using Beacon. If the driver is not using Beacon, these plugin points are disabled, the workers are never created, and the Beacon SDK managers are never instantiated, essentially eliminating Beacon’s run-time footprint.
When a driver is using Beacon, we adhere to the contract where our Workers should be attached at the lowest possible RIB. An example of this is our Beacon Alerts worker, which produces in-app alerts for Beacon such as “Low Battery” or “Beacon Not Found.” Imagine a driver checking their earnings in the app at home, a status we refer to as Offline since the driver is not being matched with riders; the driver isn’t using Beacon and shouldn’t receive these types of alerts. We only attach the Beacon Alerts Worker when the driver is on the road picking up riders, referred to as Online, since that is when these alerts matter.
End-to-end color matching
With the Beacon framework in place, we can leverage the rider app to drive the Color Matching user experience.
This flow begins with the driver connecting to Beacon and going Online. When the driver is Online, we attach a Beacon Capability Worker to the Online RIB, which updates our backend with the driver’s Beacon capability. As the driver connects and disconnects from Beacon, we update the capability accordingly. When the driver goes Offline, the backend automatically updates the capability to false.
Then, when the driver accepts a ride, the rider’s app receives the driver’s name and vehicle information, along with their Beacon capability. If the capability is true, we display a Beacon icon in the rider app. In return, the rider app automatically sends a previously selected color (or a randomly selected color if the rider has never chosen a color before) to our backend, which notifies the driver app of the desired color.
While waiting to be picked up, the rider can tap the Beacon icon and we display a 24-bit color wheel (with certain colors, such as red and blue, removed for safety and compliance) where the rider can pick any color they desire. Each time the rider selects a color, we send that color to the backend, which in turn notifies the driver app.
On the driver side, we have a Beacon Color Worker that listens for these color notifications whenever the driver is Online. As we receive colors, we store a local map of Rider+Color pairs. This way, if the driver has accepted multiple dispatches–such as an uberPOOL scenario–we track the latest unique color for each rider that will be picked up.
As the backend informs the driver app we have entered the pickup zone for a specific rider, we send a command from our Beacon Color Worker to the Beacon LED manager, telling it to display that rider’s color and–Voila!–we have matched the rider to the driver via our color matching technology. Each subsequent pickup will display that rider’s color. Additionally, during pickup, we skip the color map and immediately display any color the rider subsequently chooses, keeping the apps, and hence the rider and driver, in sync.
The fusion of mobile and hardware
Uber is always searching for ways to improve our platform experience. Extending the platform to the physical world through mobile-connected hardware is a natural progression. The fusion of mobile and hardware creates new, exciting challenges to bridge our apps into the physical world, and Uber Beacon is just one example of how Uber is innovating in this space. It’s an exciting time to be a mobile engineer at Uber!
Index of articles in Uber driver app series
- Why We Decided to Rewrite Uber’s Driver App
- Architecting Uber’s New Driver App in RIBs
- How Uber’s New Driver App Overcomes Network Lag
- Scaling Cash Payments in Uber Eats
- How to Ship an App Rewrite Without Risking Your Entire Business
- Building a Scalable and Reliable Map Interface for Drivers
- Engineering Uber Beacon: Matching Riders and Drivers in 24-bit RGB Colors
- Architecting a Safe, Scalable, and Server-Driven Platform for Driver Preferences
- Building a Real-time Earnings Tracker into Uber’s New Driver App
- Activity/Service as a Dependency: Rethinking Android Architecture in Uber’s New Driver App
Uber Beacon took a massive team effort to build, with many individuals responsible for all the steps that got us to this point:
- Peter Oliver, iOS engineer and co-owner of the the Uber Beacon experience
- Janie Gu, Product Manager and master wrangler of hardware and software
- Ramit Hora, Technical Program Manager for SiR
- Terry Worona, iOS engineer for SiR
- Ernel Murati, Android engineer for SiR
- Nikhil Goel, original Product Manager for color matching technology
- Sami Aref, iOS engineer, original implementation on iOS
- Guillame Lachaud, Android engineer, original implementation on Android
- John Badalamenti, Product Design