Building the New Uber Freight App as Lists of Modular, Reusable Components
August 22, 2019 / GlobalAs Uber Freight marked its second anniversary, we went back to the drawing board to redesign its app. The original carrier app was successful for owner-operators with one or two drivers, but it wasn’t optimized for larger fleets—feedback we were hearing directly from customers. It let carriers find and move freight from point A to point B, but did not support multi-stop loads with multiple pick-ups and drop-offs.
On the engineering side, our team was growing in size. Each piece of technical debt became another unnecessarily complex example in our codebase. Our engineering velocity decreased—simple features took weeks to deploy. Efficiency was not scaling with increased headcount.
We saw the app redesign effort as a great opportunity to rethink how our app was built. Internally, we wanted to ensure that the sum of our team was greater than the individual parts. Externally, we wanted to quickly give carriers new features and improved user experiences without compromising quality.
Component-driven development
In 2016, Uber’s Mobile Platform team rewrote our rider app on a new (now open source) mobile architecture called RIBs. In 2017, the Driver team rewrote our driver app and adopted that same architecture.
In an effort to future-proof the app, the original Uber Freight app was written using the RIBs architecture from the very start. For Uber Freight’s launch in May 2018, the app was built by combining multiple RIBs, structures consisting of routers, interactors, and builders that make up the architecture of Uber’s open source, cross-platform, mobile architecture. It had good code isolation, a well thought out RIB tree with deep scopes, and decent separation of concerns.
However, many RIBs in the app’s structure grew too big and complex. It became difficult to share and reuse similar pieces of the UI and logic without refactoring major parts of the app.
The issue was that the design of the Uber Freight app relied heavily on lists. New features were integrated into existing feeds. List RIBs grew in size and complexity, and became difficult to maintain. Different lists often shared smaller pieces of UI and experiences, but the similar parts were buried within large classes, which discouraged code reuse.
By itself, the RIBs architecture wasn’t granular enough to facilitate UI and logic reuse. Code duplication became a problem, which made development frustrating and achieving consistency difficult.
In an effort to increase efficiencies and improve developer velocity, we decided to build a component-driven framework to complement the existing RIBs architecture. UI and logic could be broken down into even smaller pieces within RIBs, and our entire app could be composed of these individual, self-contained components.
Building the new framework
We built our new framework leveraging four major steps, each addressing a specific need:
- Since the app is list heavy, we built a ListView UI framework using RecyclerViews and UICollectionViews to compose arbitrary UI components. The Uber Freight app primarily lists loads for carriers and their drivers. Users can scroll through multiple feeds displaying new, current, and past loads, load details, entry points to post their trucks and wait for matched opportunities, information about freight facilities, recommendations, and many other products and features.
- Since the app shares a consistent design pattern throughout multiple features and screens, we built modular, reusable ListView components. While RecyclerViews and UICollectionViews are generally used to display dynamic lists of content, we chose to build both dynamic and static screens by composing components together. This way, each component can be easily reused in other features without needing to duplicate the UI, presentation logic, or business logic.