Our 2016 rewrite of the Uber rider app resulted in a feature-rich and streamlined experience, with support for Uber’s range of products, from uberPOOL to uberXL, and extensibility to future experiences, such as JUMP Bikes. The Uber rider app works across markets worldwide, displaying 50 languages and 30 payment methods–and counting.
Although we built the app to be as efficient as possible, its features brought the size to over 60 megabytes, used higher network bandwidth, and required specific hardware performance from riders’ phones.
While our new app has shown increased usage globally, our data shows that rider devices and network connectivity in regions such as Latin America, India, and the Middle East frequently do not meet these requirements. For example, 56 percent of Global Uber riders used Android phones from Year Class 2015 or earlier. On this hardware, our new rider app might not offer the best experience. We wanted to serve the broadest possible base of riders, and give them the most seamless transportation experience possible.
To address this challenge, we built Uber Lite, a rider app designed for use on older Android devices and in areas where network infrastructure may not reliably serve LTE data connectivity.
Motivation behind Uber Lite
In 2017, a task force made up of members from Uber’s research, design, product and engineering teams was formed to better understand and address the pain points for riders with older Android devices in low connectivity regions. While researchers and designers set out to understand rider behavior and come up with a simplified UI, engineers assessed our existing rider app to see if it could be restructured and trimmed down to make it more lightweight.
Deep engineering analysis revealed that simply trimming down the app would not suffice. Instead, we came up with a fresh architecture that delegated Android client communication responsibilities to a well-orchestrated, light back-end layer specifically serving network calls to our infrastructure. This new mobile back-end communication model and information architecture also served to reduce bandwidth usage without bloating the Android app size.
Our work leading up to the launch of Uber Lite in 2018 also involved research into the requirements of riders around the world, taking into account their available phones and wireless networks. From a strict set of requirements, our engineering team made design decisions that minimized the phone and network resources required for the app. The Uber Lite app reflects our acknowledgment that the need for transportation transcends both regions and economies.
Product design considerations
The latest iteration of our rider app kept pace with the demands of riders who use the most recent mobile phone hardware, have access to fast data networks, or enjoy inexpensive data plans. However, the real world has diversity–data shows that in India alone, 46 million Uber riders use Android phones with technology equivalent to a device from 2013 (referred to as a Class 2013 phone) or older. Around the world, 40 percent of Uber rides are booked via Android phones with hardware dating from 2014 or earlier, while 33 percent occur over sub-3G data networks.
To design a high-performance app for these conditions, we stuck to three guiding principles: light, instant, and simple.
Based on our research, we determined that the new app should exhibit a light look, feel, and operation, with both the download and install using as little bandwidth and memory as possible.
- Under 5 megabytes: Keeping the rider app’s download size under 5 megabytes (a memory size that amounts to less than three selfies) and the on-device size under 25 megabytes enables blazing fast app install and download speeds even in degraded network conditions or on Android phones with hardware from 2015 or earlier.
- Maps on tap: Uber Lite only displays maps, which take up a lot of bandwidth, if desired. Riders can optionally see a basic live map if needed. The app experience is built around reduced maps usage, making the app frugal in its data network usage.
- Server-driven client: Uber Lite leverages a thin orchestration layer of services and back-end components to calculate, compute, and present jobs that need heavy lifting and orchestration of data from Uber’s platform. This engineering strategy helps reduce network payloads to less than 1 maximum transmission unit (MTU).
The new app should be highly responsive, even on wireless networks operating at sub-3G speeds.
- Eager to request: The new app was engineered to deliver less than 300 millisecond response times between one screen and the next while traversing through the core flow.
- Retain a single Dalvik Executable (DEX): Android apps become multi-DEX when the total number of methods in the Android application package (APK) exceed 65,536. Use of smart compilation techniques combined with efficient libraries and dependency design ensure single DEX for the Uber Lite app, even with all of the rider app’s core features.
- Real-time notifications: Uber Lite supports real-time notifications that deliver important alerts to riders.
Using an easily understood five-step flow to hail a ride helps riders feel in control of their experience, and leads to a better ratio of completed rides over sessions received on the app, as opposed to with a more robust app that may not perform well on older class hardware or on slower networks.
- Guided pickups: Uber Lite simplifies destination entry by using points of interest (POIs) instead of addresses for pick-up locations. POIs tend to be short distances from actual locations in emerging regions and are easily recognizable by drivers, making them useful landmarks for pick-ups.
- Tappable destinations: Uber Lite adds to the list of destinations presented to riders based on their ride history and frequency by caching them for offline access and fast selection in poor network conditions. The new app’s UI also lets users tap on suggested destinations rather than typing them out.
- Safety: Riders can easily share their trips with family and friends, keeping safety at the center stage for Uber Lite. This feature is similar to the shared trip status feature in the full rider app.
- Language selection: Localization is at the heart of the Uber Lite design. Many languages, such as Portuguese, Spanish, and Hindi, are available to users out-of-the-box.
Exploring lean libraries
Libraries take up a significant amount of any app’s data size, so as we built Uber Lite, we wanted to be very particular as to which libraries we would include. We carefully evaluated the size, memory, and network usage for a number of Uber-developed and third-party libraries. Because of our unique use case and aggressive goals around size and method count, we worked with different internal library owners to split them into smaller modules, using only the features we really needed. For the libraries which were already modular, we were careful to pick only the most essential ones.
As one example, we use RIBs, Uber’s open source cross-platform mobile architecture framework, for our app architecture. The main rider app separates core code from non-core code through RIBs’ plugin framework, resulting in a system in which the core flow, its basic functionality, can be completely isolated from non-core code. However, this framework increased the payload size of our network calls. To prevent this from occurring with Uber Lite, we decided not to use RIBs’ plugin framework.
Additionally, since we needed fewer screen transitions in Uber Lite, we created small modules in the RIBs screen stack with just native transitions. As a result, we were able to remove extra dependencies of around 200 kilobytes.
In addition to limiting the size of our libraries, we have also reduced the app size of Uber Lite by:
- Shipping only resources specific to app locations, such as strings, to prevent the app from growing too large.
- Using vector image formats via Uber’s existing linting structure to prevent the need for PNG resource check-ins.
- Removing excessive methods from Uber Lite bytecode generated from actions such as synthetic accessors and covariant overrides.
Choosing an architecture design
Researching existing lightweight apps from other companies, we saw three design paradigms: WebView, a native app architecture with a server-rendered UI, and a standard native app architecture. After exploring these different options, we decided to go with a native app design.
Using a native shell virtual machine, with the content and UI pushed from the server, means there is no product code in the app’s binary. The advantages of this approach are a tiny app download and on-disk size, as well as the ability to add features without the additional bloat of native code. However, continually pushing UI components to riders’ phones made the payload too large, so we explored the following alternatives:
- React Native/Flutter: While flexible, the size of the library, at 6 to 8 megabytes, made this approach a non-starter.
- WebVew delivered over Uber’s mobile website: Using a WebView layer on top of our our homegrown, web-based interface initially seemed like an attractive option, but as we experimented with it, we found multiple issues such as fragmentation across different Android versions, inability to integrate critical features, poor performance, and difficulty using the existing mobile infrastructure.
While we decided to use the native app approach, we use dynamic UI-based frameworks for non-critical flows that are ancillary to the core rideshare experience, such as payments, help, and support.
Uber Lite information flow
The slowest action for an app is often fetching data from the network. Low connectivity in developing markets adds to this problem. We made eight key design decisions when building the information flow for Uber Lite to accommodate 2G connectivity:
- Use server-side events for background updates. Background updates are essential to keep data fresh without impacting the rider’s experience. Pushing background updates from the server lets us control the time and frequency of updates.
- Push information ahead of time. Pushing information, such as types of rides and payment options, just after getting online reduces the overhead of network call overtime and improves the rider experience by making product information available in the app UI even when offline.
- Push information on changes. We push data concerning state changes to the app in real time, so the rider is kept informed of actions such as when a driver accepts their trip.
- Allow only one network request per screen. Federating network requests increases network performance, enhancing the rider experience.
- Use a single TCP connection. On a 2G network, where upload speeds are far lower than download speeds, simply setting up the connection can take more time than transferring data. Keeping data request and response sizes less than 1 MTU ensures that only one TCP connection needs to be made.
- Avoid redundant communication. Avoiding duplicate and unnecessary information over the wire reduces network utilization. For example, instead of polling information at regular intervals, sending information only during when status changes occur helps the app avoid redundant communication.
- Decide actions on the back end. Let the back end decide what it needs to send to the app rather than requiring unnecessary computation in the app.
- Cache data in client. Uber Lite caches static data to avoid network calls such as product and payment profiles for particular users.At the same time, we also worked on offline search by smartly caching the locations when the user is on wifi and full charge.
Uber Lite’s journey has just begun, and its future looks promising. Over time, we will continue to build new features while also looking for ways to keep the footprint of the app in check and stay true to our design principles of being light, instant, and simple. Below, we discuss a few future Uber Lite additions we intend to make to adhere to these guidelines:
WebViews/Dynamic UI for non-frequent flows
We have already begun exploring strategies to use WebView or a dynamic UI framework for certain non-frequent flows in the app. We want to try different caching strategies in WebViews to make them faster on slower networks. Optimizing caching should help in scaling the app for the long term without impacting its size. Choosing a native experience versus optimizing the application size is a conscious tradeoff we are making with the idea that we want to keep the flows which are most important to the user native and provide a smoother experience.
ProGuard, ReDex, and app bundles
We are also exploring strategies to make third party libraries leaner. While it is easy to clean up Uber’s own internal libraries, open source libraries like ReactiveX and Dagger constitute a big portion of Uber Lite’s size. While we are already using ProGuard to optimize the APK, we are actively looking for further optimizations with ProGuard and ReDex. At the same time, we are also exploring new solutions, such as app bundles, to quickly ship new features.
Keeping the app small is difficult, and a single large commit can throw off our size constraints. To maintain our design principles, we are actively working on tools to prevent multidexing, generate app size metrics, remove unused dependencies, and whitelist packages to prevent the need for new libraries via transitive dependencies. We are also enhancing our CI pipelines to throw these errors so that developers can get real-time feedback with every pull request.