Uber RIBs Architecture -Part 4

Create Builder

Mohit Sharma
4 min readJun 19, 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

In this Part we will write our LoggedOutBuilder, which is the most complex part of RIBs architecture.

Work of Builder in RIBs is create the instances of Router, Interactor and View for the RIB.
It also handles the DI using dagger component and modules.

This part might contain some advance dagger concepts, which I will try to explain in as simple terms as possible.

Lets create our LoggedOutBuilder

We will create a new class LoggedOutBuilder and extend it with ViewBuilder<V, R, D> which is base class for the builder of RIB with UI.

V = View, we will use LoggedOutView for this.

R = Router, we will use LoggedOutRouter for this.

D = external dependecy, we will create a new interface with name ParentComponent inside our builder for this.

Now our code looks like this.

import com.uber.rib.core.ViewBuilder

class LoggedOutBuilder(dependency: ParentComponent) :
ViewBuilder<LoggedOutView, LoggedOutRouter, LoggedOutBuilder.ParentComponent>(dependency) {

interface ParentComponent {
// For all the dependency from Parent RIB.
}
}

At this stage, you will compilation error in Android Studio, that we have to implement inflate view method.
We have to use it to inflate our LoggedOutView.

import android.view.LayoutInflater
import android.view.ViewGroup
import com.uber.rib.core.ViewBuilder
import com.uber.rib.tutorial1.R

class LoggedOutBuilder(dependency: ParentComponent) :
ViewBuilder<LoggedOutView, LoggedOutRouter, LoggedOutBuilder.ParentComponent>(dependency) {

interface ParentComponent {
// For all the dependency from Parent RIB.
}

override fun inflateView(inflater: LayoutInflater, parentViewGroup: ViewGroup): LoggedOutView {
return inflater.inflate(R.layout.logged_out_rib, parentViewGroup, false) as LoggedOutView
}
}

DI Setup

Builder is also responsible for handling the dependency injection.

Lets first create a LoggedOutScope that we will use to set the scope of all the dependecy.

We will write everything inside LoggedOutBuilder class itself.

    @Scope
@Retention(value = AnnotationRetention.RUNTIME)
annotation class LoggedOutScope

Lets create our dagger Component.

    @LoggedOutScope
@dagger.Component
interface Component {

@dagger.Component.Builder
interface Builder {

fun build(): Component
}
}

We will have our custom Builder for Component to inject dependencies from Parent RIB.

Lets create our dagger Module now.

Lets start adding our provider methods. We will create provider for below requirements.

LoggedOutInteractor.Presenter

Our Presenter will be implemented by LoggedOutView. So we will use the LoggedOutView to provide us the instance of Presenter.

class LoggedOutView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs), 
LoggedOutInteractor.Presenter {

}

And our module will look like.

   @dagger.Module
class Module {

@LoggedOutScope
@Provides
fun loggedOutPresenter(loggedOutView: LoggedOutView): LoggedOutInteractor.Presenter {
return loggedOutView
}
}

LoggedOutRouter

        @LoggedOutScope
@Provides
fun loggedOutRouter(view: LoggedOutView,
interactor: LoggedOutInteractor,
component: Component): LoggedOutRouter {
return LoggedOutRouter(view, interactor, component)
}

Now we will get the below compilation error.

Our Component has to implement InteractorBaseComponent class. Lets add that to our Component.

    @LoggedOutScope
@dagger.Component
interface Component: InteractorBaseComponent<LoggedOutInteractor> {

@dagger.Component.Builder
interface Builder {

fun build(): Component
}
}

But what does, InteractorBaseComponent do?

/**
* Designates a component that can provide a specific interactor.
*
* @param <T> type of interactor that is injected.
*/
public interface InteractorBaseComponent<T : Interactor<*, *>> {
/**
* Inject the interactor.
*
* @param interactor to inject.
*/
public fun inject(interactor: T)
}

It only provides injection into the interactor.
So now LoggedOutInteractor is injectable by dagger now.

Now our module need LoggedOutView and LoggedOutInteractor to provide the dependecy. We will take them from the component creator. So we will add them inside our component Builder.

    @LoggedOutScope
@dagger.Component(modules = [Module::class])
interface Component: InteractorBaseComponent<LoggedOutInteractor>{

@dagger.Component.Builder
interface Builder {

@BindsInstance
fun view(loggedOutView: LoggedOutView): Builder

@BindsInstance
fun interactor(loggedOutInteractor: LoggedOutInteractor): Builder

fun build(): Component
}
}

Now we will also create a new interface BuilderComponent, which will contains the providers from this component to child RIBs.

interface BuilderComponent {
fun loggedOutRouter(): LoggedOutRouter
}

We will extend this to our Component.

    @LoggedOutScope
@dagger.Component(modules = [Module::class], dependencies = [ParentComponent::class])
interface Component: InteractorBaseComponent<LoggedOutInteractor>, BuilderComponent {

@dagger.Component.Builder
interface Builder {

@BindsInstance
fun view(loggedOutView: LoggedOutView): Builder

@BindsInstance
fun interactor(loggedOutInteractor: LoggedOutInteractor): Builder

fun build(): Component
}
}

Now we can create a function to build our LoggedOutRouter inside our LoggedOutBuilder. Lets call it build.

    fun build(parentViewGroup: ViewGroup): LoggedOutRouter {
val view = createView(parentViewGroup)
val interactor = LoggedOutInteractor()
val component = DaggerLoggedOutBuilder_Component.builder()
.interactor(interactor)
.view(view)
.build()
return component.loggedOutRouter()
}

Now last thing that we have to do, is to make our Builder Component, dependent of its parent component. This is because, in RIB, dependency flows from TOP to BOTTOM. So our parent components will provide the dependency required for our LoggedOutRib.

Now finally our Builder Component looks like.

    @LoggedOutScope
@dagger.Component(modules = [Module::class], dependencies = [ParentComponent::class])
interface Component: InteractorBaseComponent<LoggedOutInteractor>, BuilderComponent {

@dagger.Component.Builder
interface Builder {

@BindsInstance
fun view(loggedOutView: LoggedOutView): Builder

@BindsInstance
fun interactor(loggedOutInteractor: LoggedOutInteractor): Builder

fun parentComponent(parentComponent: ParentComponent): Builder

fun build(): Component
}
}

We will also update our build function in LoggedOutBuilder.

    fun build(parentViewGroup: ViewGroup): LoggedOutRouter {
val view = createView(parentViewGroup)
val interactor = LoggedOutInteractor()
val component = DaggerLoggedOutBuilder_Component.builder()
.parentComponent(dependency)
.interactor(interactor)
.view(view)
.build()
return component.loggedOutRouter()
}

Now our Builder is ready to integrate with other componets.

We will cover the integration in the next part.

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

--

--