Uber RIBs Architecture -Part 1

Introduction to RIBs

Mohit Sharma
6 min readJun 18, 2023

All parts in this series.

Part 1: Introduction to RIBs
Part 2: Creation of View for logged out RIB

Part 3: Creation of Router and Interactor for logged out RIB

Part 4: Creation of Builder for logged out RIB

Part 5: Integrating Builder, Router and Interactor for logged out RIB

What is Motivation for this blog series?

When I started learning RIBs architecute from https://github.com/uber/RIBs I found, it to be little hard. I didn’t find any good resource on internet which could explain this architecture in a simple and easy flow. So I decided to write a blog on it, to make it easier for other folks to learn about it.

We will write a RIB without using the tooling to learn about what is happening here.

Before starting with the implementation part. Lets get some basic knowledge on it.

What is RIBs?

RIBs is the cross-platform architecture framework behind many mobile apps at Uber. The name RIBs is short for Router, Interactor and Builder, which are core components of this architecture. This framework is designed for mobile apps with a large number of engineers and nested states.

The RIBs architecture provides:

  • Shared architecture across iOS and Android. Build cross-platform apps that have similar architecture, enabling iOS and Android teams to cross-review business logic code.
  • Testability and Isolation. Classes must be easy to unit test and reason about in isolation. Individual RIB classes have distinct responsibilities like: routing, business, view logic, creation. Plus, most RIB logic is decoupled from child RIB logic. This makes RIB classes easy to test and reason about independently.
  • Tooling for developer productivity. RIBs come with IDE tooling around code generation, memory leak detection, static analysis and runtime integrations — all which improve developer productivity for large teams or small.
  • An architecture that scales. This architecture has proven to scale to hundreds of engineers working on the same codebase and apps with hundreds of RIBs.

Parts of RIBs

Interactor

An Interactor contains business logic. This is where you perform Rx subscriptions, make state-altering decisions, decide where to store what data, and decide what other RIBs should be attached as children.

All operations performed by the Interactor must be confined to its lifecycle. We have built tooling to ensure that business logic is only executed when the Interactor is active. This prevents scenarios where Interactors are deactivated, but subscriptions still fire and cause unwanted updates to the business logic or the UI state.

Router

A Router listens to the Interactor and translates its outputs into attaching and detaching child RIBs. Routers exist for three simple reasons:

  • Routers act as Humble Objects that make it easier to test complex Interactor logic without a need to to mock child Interactors or otherwise care about their existence.
  • Routers create an additional abstraction layer between a parent Interactor and its child Interactors. This makes synchronous communication between Interactors a tiny bit harder and encourages adoption of reactive communication instead of direct coupling between the RIBs.
  • Routers contain simple and repetitive routing logic that would otherwise be implemented by the Interactors. Factoring out this boilerplate code helps to keep the Interactors small and more focused on the core business logic provided by the RIB.

Builder

The Builder’s responsibility is to instantiate all the RIB’s constituent classes as well as the Builders for each of the RIB’s children.

Separating the class creation logic in the Builder adds support for mockability on iOS and makes the rest of the RIB code indifferent to the details of DI implementation. The Builder is the only part of the RIB that should be made aware of the DI system used in the project. By implementing a different Builder, it is possible to reuse the rest of the RIB code in a project using a different DI mechanism.

Presenter

Presenters are stateless classes that translate business models into view models and vice versa. They can be used to facilitate testing of view model transformations. However, often this translation is so trivial that it doesn’t warrant the creation of a dedicated Presenter class. If the Presenter is omitted, translating the view models becomes a responsibility of a View(Controller) or an Interactor.

View(Controller)

Views build and update the UI. This includes instantiating and laying out UI components, handling user interaction, filling UI components with data, and animations. Views are designed to be as “dumb” as possible. They just display information. In general, they do not contain any code that needs to be unit tested.

Component

Components are used to manage the RIB dependencies. They assist the Builders with instantiating the other units that compose a RIB. The Components provide access to the external dependencies that are needed to build a RIB as well as own the dependencies created by the RIB itself and control access to them from the other RIBs. The Component of a parent RIB is usually injected into the child RIB’s Builder to give the child access to the parent RIB’s dependencies.

State Management

Application state is largely managed and represented by the RIBs that are currently attached to the RIB tree. For example, as the user progresses through different states in a simplified ride sharing app, the app attaches and detaches the following RIBs (see GIF below).

State transition in RIBs

RIBs only make state decisions within their scope. For example, the LoggedIn RIB only makes state decisions for transitioning between states like Request and OnTrip. It does not make any decisions about how to behave once we are on the OnTrip screen.

Not all state can be stored by the addition or removal of the RIBs. For example, when a user’s profile settings change, no RIB gets attached or detached. Typically, we store this state inside the streams of immutable models that re-emit values when the details change. For example, the user’s name may be stored in a ProfileDataStream that lives inside the LoggedIn scope. Only network responses have write access to this stream. We pass an interface that provides read access to these streams down the DI graph.

There is nothing in RIBs that forces a single source of truth for the RIB state. This is in contrast to what more opinionated frameworks, like React, already provide out of the box. Within the context of each RIB, you can choose to adopt patterns that promote unidirectional data flow, or you can allow business state and view state to temporarily diverge in order to take advantage of efficient platform animation frameworks.

RIB Tooling

In order to ensure smooth adoption of the RIB architecture across our apps, we have invested in tooling to make RIBs easier to use and take advantage of the invariants created by adopting the RIBs architecture. Some of this tooling has been open sourced and will be discussed in tutorials.

The RIB related tooling that Uber has so far open sourced includes:

In the next part, we will go through RIBs code and create our own RIB without any tooling.

All parts in this series.

Part 1: Introduction to RIBs
Part 2: Creation of View for logged out RIB

Part 3: Creation of Router and Interactor for logged out RIB

Part 4: Creation of Builder for logged out RIB

Part 5: Integrating Builder, Router and Interactor for logged out RIB

--

--