The invaluable feedback received from driver-partners fueled the inception of a user experience that transcends the conventional. This article outlines how Uber RIBs came into play, how they developed the primary requirements for their new driver app and how RIBs architecture and plugin design were used.
In the endeavour to reimagine Uber’s driver app, the mission was clear: not merely to create an aesthetically pleasing and functionally robust interface but to decode the unarticulated needs of the drivers navigating the streets daily.
So, the challenge was confronted with ensuring that the architectural backbone of the app serves not just as a conduit for features but as a sophisticated framework facilitating seamless development and future iterations. The vision was to empower engineers—present and future—to bring features to life swiftly without compromising reliability.
The unveiled strategy enabled harmonising the needs of a diverse user base and the engineering team, employing a blend of RIBs architecture and plugin design patterns.
What are Uber RIBs?
Uber RIBs, or Router, Interactor, and Builder, are Uber’s distinguished cross-platform architecture framework. Developed primarily on addressing the complexities inherent in large mobile applications boasting numerous nested states. Uber RIBs robustly streamlines development, encourages collaboration, and enhances the overall efficiency of engineering teams.
Cross-Platform Collaboration: One of the key principles guiding the development of UBER RIBs is to foster cross-platform collaboration. Recognizing the similarity of complex components in Uber’s apps on both iOS and Android, RIBs introduces uniform development patterns.
By adopting RIBs, engineers working across both platforms can leverage a singular, co-designed architecture for their features. This simplifies the development process and ensures consistency in the application’s functionality.
Minimizing Global States and Decisions: Global state changes can lead to unpredictable behaviour and hinder the ability of engineers to comprehend the full impact of their modifications. UBER RIBs address this challenge by advocating for the encapsulation of states within a deep hierarchy of well-isolated individual RIBs. This approach effectively mitigates global state issues, promoting stability and predictability in the application’s behaviour.
Testability and Isolation: Ensuring that classes are easy to test and reason about in isolation is a critical aspect of software development. UBER RIBs adhere to this principle by endowing individual RIB classes with distinct responsibilities. These include routing, business logic, view logic, and creating other RIB classes. Decoupling parent RIB logic from its child RIB logic enhances testability and facilitates independent reasoning about each RIB class.
Tooling for Developer Productivity
Adopting sophisticated architectural patterns necessitates robust tooling for scalability. UBER RIBs come equipped with IDE tooling encompassing code generation, static analysis, and runtime integrations.
Collectively, these tools contribute to heightened developer productivity for both large and small teams, ensuring a seamless and efficient development process.
Open-Closed Principle
In adherence to the open-closed principle, UBER RIBs enable developers to add new features without modifying existing code wherever possible.
This flexibility is particularly evident in the ability to attach or build complex child RIBs requiring dependencies from their parent with minimal changes to the parent RIB.
Structured around Business Logic
UBER RIBs acknowledges the need for flexibility in structuring an app’s business logic independently of its UI. The framework accommodates scenarios where the view hierarchy may differ from the RIB hierarchy to optimize animations and view performance. A single feature RIB, for instance, can seamlessly control the appearance of multiple views located at different points in the UI.
Explicit Contracts
Requirements within UBER RIBs are declared through compile-time safe contracts. Classes will not compile if their class dependencies and ordering dependencies are not satisfied. The framework leverages ReactiveX to represent ordering dependencies and employs type-safe dependency injection (DI) systems to signify class dependencies, ensuring a robust and structured development environment.
What are the Parts of RIBs?
When it comes to dissecting the Uber RIBs architectural framework, understanding the core elements that compose an RIB (Router, Interactor, and Builder) is paramount. If you’ve previously worked with the VIPER architecture, breaking an RIB’s class structure may ring familiar bells. Let’s explore the distinct components that make up the anatomy of an RIB and the crucial role each plays in shaping the robust architecture of Uber’s applications.
Router
A Router acts as the conduit between the Interactor and the intricate dance of RIBs. It listens to the Interactor, translating its outputs into the attachment and detachment of child RIBs.
Routers serve multiple purposes, acting as Humble Objects for testing complex Interactor logic. This creates an abstraction layer between parent and child Interactors to encourage reactive communication and handle repetitive routing logic to keep Interactors focused on core business logic.
Interactor
Interactor is a domain where business logic takes centre stage. Here, one orchestrates Rx subscriptions, navigates state-altering decisions, determines data storage locations, and dictates which RIBs should be attached as children.
Crucially, the Interactor confines its operations within its lifecycle, thanks to built-in tooling. This ensures that business logic executes only when the Interactor is active, preventing undesired updates during deactivation scenarios.
Builder
The Builder, a linchpin in RIB creation, is responsible for instantiating all the constituent classes within a RIB and the Builders for each of its children. By separating the class creation logic, the Builder supports mockability on iOS.
This keeps the rest of the RIB code oblivious to the details of Dependency Injection (DI) implementation. It is the only part of the RIB aware of the DI system used in the project, allowing the reuse of RIB code in different projects with varying DI mechanisms.
Presenter
Presenters, stateless entities within RIBs, serve as translators between business models and view models. While they can aid in testing view model transformations, their use is often trivial, and in many cases, the translation responsibility falls on the shoulders of a View(Controller) or an Interactor.
View(Controller)
Views are the architects of the user interface within RIBs. They handle tasks such as instantiating and arranging UI components, managing user interaction, populating UI components with data, and orchestrating animations. Designed to be as “silly” as possible, Views focus solely on displaying information and typically lack code requiring unit testing.
Component: Managing RIB Dependencies
Components play a crucial role in managing RIB dependencies. They assist Builders in instantiating the various units that compose a RIB, providing access to external dependencies needed to build a RIB, owning dependencies created by the RIB itself, and controlling access to them from other RIBs.
The Component of a parent RIB is commonly injected into the child RIB’s Builder, enabling the child to access the parent’s dependencies seamlessly.
The 4 Phases of Uber RIBs Project
Uber meticulously crafted a strategic approach, dividing the project into four distinct phases. Each phase was designed with a specific goal, ensuring a systematic progression that laid the foundation for the subsequent stages.
In this section, we delve into the intricacies of each phase, unraveling the thought process and achievements that characterize the evolution of the Uber RIBs project.
Phase 0: Infrastructure – Laying the Groundwork for Excellence
The inaugural phase of our journey focused on establishing a robust infrastructure. Internally, we had a predefined template encompassing essential libraries for networking, storage, ReactiveX, analytics tracking, crash reporting, and our homegrown application architecture framework, RIBs.
In this phase, we built a skeletal app, devoid of features but equipped with the necessary components for storage, networking, crash reporting, and overall infrastructure. This served as the foundational scaffold upon which we would progressively build our app’s features.
Phase 1: Application Root – Defining User States and Structure
With the infrastructure in place, we turned our attention to the application’s root. Leveraging the business logic-centric approach of RIBs architecture, we defined high-level user states for drivers.
This led to the creation of foundational RIBs, including Root (bootstrapping the application), Logged Out (for user authentication), Logged In (after successful authentication), and Active (ensuring the driver has a valid session and is permitted to use the application).
The focus on user states allowed us to decouple from the UI, facilitating the incorporation of ongoing feedback from driver-partners into the application design.
Phase 2: Feature Frameworks – Scaling Development for Collaboration
As we transitioned into Phase 2, collaboration took center stage. The goal was to scale development, enabling around 40 teams to work seamlessly in parallel on the same app.
In collaboration with design and product teams, we defined detailed RIBs and components, introducing feature frameworks such as On Task (for driver activities), Agenda (Trip Planner), My Hub (business management for drivers), and a new RIBs-oriented map library for map-related features.
This phase significantly expanded our RIBs tree, setting the stage for enhanced functionality and parallelized development.
Worker, Plugins, and Core/Non-Core Components – Crafting a Flexible Architecture
Integral to our RIBs architecture are Workers, Plugins, and the concept of Core and Non-Core Components. Workers manage lifecycles, Plugins offer a scalable feature flagging mechanism, and Core/Non-Core Components provide flexibility in defining essential and optional functionalities.
This design pattern allows us to define core and non-core components, ensuring essential features and enabling optional ones. Through a closer look at a section of our RIBs tree, we observe multiple areas of integration, providing both flexibility in adding features and parallelization of development.
Phase 3: All Aboard – Unleashing Development for Feature Teams
In the third phase, we opened the floodgates for Carbon development and onboarding feature teams to contribute to the project.
The meticulous implementation of a plugin framework paid dividends, ensuring that every feature built moving forward remained independent. Feature incorporation became seamless, keeping most code wrapped in plugins for optional integration into our architecture.
Stuck on your Cross-Platform App Development? TechAhead is there for you
TechAhead emerges as a strategic partner, leveraging over 14 years of expertise in multi-platform app development. Discover why opting for TechAhead is the smart choice for turning your cross-platform app aspirations into a resounding success.
Exceptional Team Power
Our success stems from a diverse team of over 200 skilled professionals across various fields. Leverage the synergy of our diverse skills and experience for a comprehensive development journey.
Unparalleled Experience
With a track record boasting the successful delivery of over 2000 mobile applications across various platforms and industries, TechAhead stands as an industry pioneer. We excel in providing creative, scalable, and robust digital and mobile solutions, making us the top choice for brands.
Fusion of Creative Brilliance
TechAhead combines the creative brilliance of our designers and UI/UX experts with the dedicated development. This fusion offers you the best-in-class services, ensuring a seamless integration of aesthetics and functionality into your cross-platform app.
Impeccable Industry Leadership
As an industry leader, TechAhead has continuously set the standard for delivering cutting-edge solutions within the digital realm.
During our 14 years of journey, we’ve garnered the trust of global brands such as American Express, Disney, Audi, AXA, ICC, and more. Our commitment to a vision of excellence has consistently positioned us as a reliable partner in the tech landscape.
Business-Centric Expertise
TechAhead’s cross-platform app development company has not only served over 700 global brands but has also ensured their utmost satisfaction. Our business-centric approach is tailored to meet each client’s unique growth and product requirements.
This approach has established us as a preferred choice for companies seeking unparalleled expertise.
Conclusion
In conclusion, the fusion of user-centric design and innovative engineering through Uber RIBs has been transformative. Decoding the nuanced needs of drivers and harmonizing them with engineering efficiency, Uber crafted a groundbreaking architecture. Undoubtedly, Uber RIBs serves as the backbone, fostering collaboration and robust development.
The strategic phased approach, from infrastructure establishment to feature-rich implementation, showcases Uber’s commitment to excellence.