This article is the ninth 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.
Our driver app serves as the most important daily communication tool between Uber and our driver-partners. The app shows drivers where they can pick up the next rider, the most efficient route to get riders where they want to go, and how much money they make for each trip.
Among many other new features, the latest version of our driver app greatly improves on this last point in what we call the Real-time Earnings Tracker, a means of showing drivers how much they have earned per trip, day, and week, among other functions. Relying on direct feedback from our driver-partners, we engineered this feature to not only show how much money they accumulate for each driving session, but also to give a complete picture of their earnings on the platform.
The Real-time Earnings Tracker UI includes a series of cards, elements that overlay the app, showing things such as the last trip results and daily earnings. Drivers can select from three different modes, shown in Figure 1, below, which let them see their earnings in real time, review summaries of previous trips and driving sessions, and celebrate key moments on the platform.
Through our inclusive design process, we initially released the app as a minimum viable product (MVP), using that version to collect feedback from driver-partners. Throughout this phase, we added requested features to the Real-time Earnings Tracker, including Privacy Mode, which lets drivers hide their real-time earnings on the app’s home screen, better swipe-ability, and graceful error handling, as shown in Figure 2, below. The success of the Real-time Earnings Tracker can be measured by fact that 86 percent of drivers use it on days when they are picking up riders, and the significant traffic increase we have seen to the daily summary.
While most of the information shown in the Real-time Earnings Tracker appears automatically based on driver activity, we also worked with other teams at Uber, such as the Incentives and New Driver Guarantees teams, to allow for greater interaction and in-app support for our driver-partners. For these teams, we collected their feedback and formalized the integration guide across product, design, and engineering for adding new cards.
The challenge of a live earnings system
Showing real-time earnings in our driver app requires interaction with a number of back-end services, including trip request, trip completion, trip storage, and earnings processing. And while we redesigned the UI, we still needed to include multiple touchpoints within the app, such as a last trip card, a daily trip card, trip history, and an earnings home page.
Before designing the Real-time Earnings Tracker, we identified three likely challenges for real-time earnings display in the previous version of our driver app: data latency and reliability, data consistency and trust, and product correlation, outlined below:
- Data latency and reliability: The previous driver app showed real-time earnings in cards on the home map screen. Designed to update immediately after each trip, the app had to pull data in real time from the backend. These frequent pulls introduced server load issues which would sometimes cause outages in the earnings display system.
- Data consistency: Data for our earnings display came from a number of different sources and services, which were not all updated at the same time. One touchpoint in the app might show obsolete data while another showed newer data, leading to a lack of consistency in what is being communicated to driver-partners. In order to provide a better user experience for driver-partners, we needed these outputs to be aligned across the app.
- Product correlation: Our user research showed that driver-partners consider earnings and incentives as highly related. However, the previous app isolated these two displays, showing earnings in cards and incentives as small buttons in the top right section of the screen, as shown in Figure 3, below:
The Real-time Earnings Tracker framework
Taking our previous experience building earnings display into the prior version of the driver app into account, and witnessing the value of the a real-time tracking platform for driver-partners, we set the following goals when designing the framework behind the Real-time Earnings Tracker:
- Make data push, as opposed to data pull, a first-class citizen, reducing server requests.
- Make data consistency a first-class citizen.
- Design a scalable and extensible architecture to support new service and card integrations.
To help achieve these design goals, we built the real-time tracker framework on top of a few existing Uber platforms, infrastructures, and architectures.
Interface with the backend
The existing Real-time-API Gateway acts as an interface between our apps and back-end services. We leveraged this service as the single source of truth for tracker card data models that need to integrate with the framework either through Uber’s push pipeline or HTTP pull requests, as shown in Figure 4, below.
Using the Realtime-API Gateway as the source of truth greatly eases new back-end integrations into the framework.
The following is snippet of data models and their explanations:
enum CardType { BROWSE = 1, BULLETIN = 2, BROWSE_AND_BULLETIN = 3, UNKNOWN = 4 } (rtapi.mobile.unknownCaseFallback = “UNKNOWN“) struct TrackerCard { 1: required string cardID, 2: required double priority, 3: required bool isValid, 4: optional TrackerCardPayload payload, 5: optional CardType cardType, 6: optional ts.TimestampInSec expiresAt, 7: optional ts.TimestampInSec lastUpdatedAt, 8: optional OutageState outageState, 9: optional bool shouldForceSwitchStatusMode, 10: optional double statusModePriority } // cardID – the single source of truth to identify a card in the Tracker framework, each time a new tracker card is introduced, a new cardID is needed // priority – for mobile ranking and display of Real-time Earnings Tracker Browse Cards, every time the app gets a response from the server, the mobile ranking system performs ranking and updates the displaying order of Real-time Earnings Tracker Browse cards // payload – the data for populating the app UI should exist // cardType – differentiate Real-time Earnings Tracker Browse Cards and Real-time Earnings Tracker Bulletin Cards which display in different Real-time Earnings Tracker modes // expiresAt – when an Real-time Earnings Tracker Card needs to be invalidated, the back-end service needs to set this field to invalidate a specific card. The back-end service shouldn’t send push to expire cards because it may have load issues if getting too many calls at the same expired time. // lastUpdatedAt – each service needs to set this field when populating data and it is displayed when the back-end service is in outage status while card data is cached on the mobile side |
Integrated back-end services
In order to ensure that our data models are as accurate as possible, each back-end service integrated into this framework needs to import the Real-time Earnings Tracker Card data models from the Real-time-API library. We also highly recommend that other teams introducing features integrate with Uber’s push pipeline for real-time updates.
To make adding new cards in the driver app scalable and extensible, integrating a new card requires adding new fields in TrackerCardPayload, the data payload schema for the framework, as shown in the code snippet below, and designing specific payload data models (e.g., TrackerRecentTripsCard) for populating data in the driver app’s UI:
TrackerCardPayload { optional TrackerRecentTripsCard trackerRecentTripsCard optional TrackerDailyEarningsCard trackerDailyEarningsCard optional TrackerWeeklyEarningsCard trackerWeeklyEarningsCard optional TrackerDxGyProgressCard trackerDxGyProgressCard optional TrackerDxGyCompletionCard trackerDxGyCompletionCard // Future card types will be included here } TrackerRecentTripsCard { required string title optional string formattedTotal optional string formattedRequestAt optional string vehicleStatusDescription optional string callToAction optional string bulletinTitle optional string lastTripUuid } |
In most cases, the payload should come from the back-end and be consumed by the mobile UI directly.
Building the Real-time Earnings Tracker with RIBs
We built the new driver app using RIBs, Uber’s open source cross-platform mobile architecture. That architecture proved useful for the complex transitions needed for the Real-time Earnings Tracker’s three different modes: Status, Browse, and Bulletin. The RIBs architecture let us make the Real-time Earnings Tracker scalable, so we can add more display cards in the future.
The Real-time Earnings Tracker RIBs tree includes four RIBs and two plugins, described below:
- TrackerEntry RIB: This RIB represents the Status mode in the Real-time Earnings Tracker, and is the child RIB of Active. When tapped by a driver-partner or upon receiving a Bulletin mode trigger, the Tracker RIB will be attached as a full screen child of TrackerEntry.
- Tracker RIB: This RIB serves as a container for the BrowseTracker and BulletinTracker RIBs.
- BrowseTracker RIB: Representing the Real-time Earnings Tracker’s Browse mode, this RIB is a container for the Browse cards, such as Daily Summary, Weekly Summary, and Last Trip, in the app UI.
- BulletinTracker RIB: Representing the Real-time Earnings Tracker’s Bulletin mode, this RIB displays Bulletin cards in the app, showing things such as when the last trip was processed and various milestones for the driver. It handles push events that trigger special bulletins.
- BrowseCardPlugin and BulletinCardPlugin: Any card displayed in the Real-time Earnings Tracker is a plugin so the card can be turned off without affecting core functions. Specifically, Browse mode integration bridges the plugin-based Browse cards, such as the weekly summary card and daily summary card, into the core functionality. Bulletin cards, such as First Trip and uberPOOL Trips Unlocked milestones, bridge plugin-based Bulletin cards into core functionality for Bulletin mode integration.
Front-end core business logic
The core business logic on the frontend happens in a component called TrackerDataManager. The component takes data streams, such as earnings and incentives, from different upstream sources, manipulates the data using operations like aggregation, ranking, and validation, and then sends them to the different the Real-time Earnings Tracker mode streams according to their cardType designation, as shown in Figure 6, below:
Each data stream input to TrackerDataManager contains a list of Real-time Earnings Tracker cards. To guarantee data consistency across the app, each integrated back-end service needs to have its own data manager to manage its Real-time Earnings Tracker cards. For example, RealtimeEarningsManager manages earnings-related cards, including Last Trip, Daily Summary, and Weekly Summary. RealtimeIncentiveManager handles incentive-related cards, including Quest Tracking.
Each stream’s data manager needs handle pull and push logic, and be self contained so as to guarantee the consistent and up-to-date data. Looking at earnings as an example, the RealtimeEarningsManager will be created as a singleton in active scope and pass down the real-timeEarningsStream as a dependency to TrackerDataManager.
Adhering to a scope isolation concept, we only want to pass minimum data down to the correct scope. To do this, we separate the outputs of TrackerDataManager into three data streams representing the three Real-time Earnings Tracker modes. Each stream will be passed to related RIB consumers to populate data:
- TrackerStatusModeStream
- TrackerBrowseModeStream
- TrackerBulletinModeStream
/// @CreateMock public protocol TrackerStatusModeListener: class { func update(selectedItem: BrowseCardContext) } /// @CreateMock public protocol TrackerStatusModeStream: class { var selectedItem: Observable<BrowseTrackerRankingItem> { get } var hasSortedValidItems: Bool { get } var getBulletinCardPush: Observable<TrackerCard> { get } var privacyStatus: Observable<TrackerPrivacyStatus> { get } } /// @CreateMock public protocol TrackerBrowseModeStream: class { var dailyDetails: Observable<[EarningsDetails]> { get } var earningsError: Observable<Error?> { get } var earningsTrackerCardsWrapper: Observable<[EarningsModelWrapper<TrackerCard>]> { get } var incentivesErrorByType: Observable<[IncentiveCardID: Error]> { get } var privacyStatus: Observable<TrackerPrivacyStatus> { get } var sortedValidItems: [BrowseTrackerRankingItem] { get } var trackerCards: Observable<[TrackerCard]> { get } func update(privacyStatus: TrackerPrivacyStatus) } /// @CreateMock public protocol TrackerBulletinModeStream: class { var bulletinCard: TrackerCard? { get } } |
Platform improvement
After integrating incentives into our Real-time Earnings Tracker, we collected useful feedback about the existing implementation from the Incentives team, such as requests for better integration integrity and a cleaner API. We were able to further improve the integration efficiency, and improve the platform to enable quicker and safer integration of new products and features:
- Use enum type to ensure compile-time safety when adding new cards, guaranteeing high integration integrity.
- Improve code isolation and API design so that adding a new card won’t affect core functionality.
- Provide basic UI templates and base card RIBs, completely removing boilerplate UI and business logic code, to improve developer productivity.
The future of the Real-time Earnings Tracker
The Real-time Earnings Tracker has been adopted by teams across our platform to increase earnings transparency and driver-partner engagement. By the second half of 2018, 24 features were integrated into the Real-time Earnings Tracker across its three different modes:
- Status mode: last trip, daily summary, consecutive trips progress, Quest progress, new driver guarantees progress, loyalty progress, earnings error, incentives error, new driver guarantees progress error, loyalty progress error.
- Browse mode: last trip, daily summary, consecutive trips progress, quest progress, new driver guarantees progress, loyalty progress, earnings error, incentives error, new driver guarantees progress error, and loyalty progress error.
- Bulletin mode: last trip completion, consecutive trips completion, quest completion, new driver guarantees completion.
Most recently, we integrated the Uber Pro program into the Real-time Earnings Tracker to help celebrate the accomplishments of our driver-partners, as shown in Figure 7, below.
In 2019, we plan to introduce even more new features onto the Real-time Earnings Tracker to further improve the driver-partner experience on our platform.
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 Real-time Earnings Into Our New Driver App
- Activity/Service as a Dependency: Rethinking Android Architecture in Uber’s New Driver App
Acknowledgments
We would like to acknowledge Nico Hinderling (iOS engineer), Robert Khoury (backend engineer), Roger Ho (Android engineer), and Shuang Yin (Android engineer) for their work on the Real-time Earnings Tracker platform and its implementation in the app.
Zebing Zong
Zebing Zong is a senior software engineer on Uber's Driver Experience team.
Posted by Zebing Zong
Related articles
Most popular
Enabling Infinite Retention for Upsert Tables in Apache Pinot
Presto® Express: Speeding up Query Processing with Minimal Resources
Unified Checkout: Streamlining Uber’s Payment Ecosystem
The Accounter: Scaling Operational Throughput on Uber’s Stateful Platform
Products
Company