Skip to main content
Uber logo

Schedule rides in advance

Reserve a rideReserve a ride

Schedule rides in advance

Reserve a rideReserve a ride

Building a Real-time Earnings Tracker into Uber’s New Driver App

March 25, 2019 / Global
Featured image for Building a Real-time Earnings Tracker into Uber’s New Driver App
Figure 1: The Real-time Earnings Tracker UI comes with three modes which let drivers view current trip earnings, summaries of trip earnings, and incentive results.
Figure 2: New features and engineering improvements have lead to greater use and satisfaction with the Real-time Earnings Tracker.
Figure 3: Our previous driver app separated earnings and incentives on its UI. On left, the previous driver app UI shows earnings displayed in cards and incentives in the top right section of the screen, and on the right, our new UI shows both incentives and earnings in the upper right.
Figure 4: The Real-time-API Gateway serves as an interface between Uber apps and our back-end services. Consequently, we use it as the source of truth for the Real-time Earnings Tracker.
enum CardType {
   BROWSE = 1,
   BULLETIN = 2,
   UNKNOWN = 4
} ( = 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
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
Figure 5: The Real-time Earnings Tracker RIBs tree includes four RIBs and two plugins.
Figure 6: TrackerDataManager handles core business logic on the app side, processing incoming data streams and populating data for the Real-time Earnings Tracker’s different modes.
/// @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 }
Figure 7: These cards show a few of the features added to the Real-time Earnings Tracker’s different modes.
Zebing Zong

Zebing Zong

Zebing Zong is a senior software engineer on Uber's Driver Experience team.

Posted by Zebing Zong