<file_sep>package com.badoo.ribs.samples.comms_nodes_1.rib.menu

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams

class MenuBuilder(
    private val dependency: Menu.Dependency
) : SimpleBuilder<Menu>() {

    override fun build(buildParams: BuildParams<Nothing?>): Menu {

        val presenter = MenuPresenterImpl()
        val viewDeps = object : MenuView.Dependency {
            override val presenter: MenuPresenter = presenter
        }

        return MenuNode(
            buildParams = buildParams,
            viewFactory = MenuViewImpl.Factory().invoke(deps = viewDeps),
            plugins = listOf(presenter)
        )
    }
}
<file_sep>package com.badoo.ribs.example.network

import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response

/**
 * Authentication Interceptor
 *
 * Intercepts the [Request] and adds the auth headers to work with the [UnsplashApi]
 */
class AuthInterceptor(accessKey: String) : Interceptor {
    private val authHeaderValue = "Client-ID $accessKey"

    companion object {
        internal const val HEADER_NAME = "Authorization"
    }

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
            .newBuilder()
            .addHeader(HEADER_NAME, authHeaderValue)
            .build()
        return chain.proceed(request)
    }
}
<file_sep>package com.badoo.ribs.samples.retained_instance_store.app

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.modality.BuildContext
import com.badoo.ribs.samples.retained_instance_store.R
import com.badoo.ribs.samples.retained_instance_store.rib.retained_instance_example.RetainedInstanceExampleBuilder

class RootActivity : RibActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_root)
        super.onCreate(savedInstanceState)
    }

    override val rootViewGroup: ViewGroup
        get() = findViewById(R.id.root)

    override fun createRib(savedInstanceState: Bundle?): Rib {
        return RetainedInstanceExampleBuilder().build(BuildContext.root(savedInstanceState))
    }
}
<file_sep># Tutorials

(In progress, check back later for more)

## Before 
[Base concepts](baseconcepts.md)

## Tutorials
1. [Building and attaching a RIB to integration point](tutorial1/README.md) (~15m)
2. [Adding a child RIB to a parent](tutorial2/README.md)
3. [Satisfying dependencies of a child RIB](tutorial3/README.md)
4. [Communicating with child RIBs](tutorial4/README.md)
5. [Routing](tutorial5/README.md)
6. TODO
7. TODO

## After
Best practices

<file_sep>package com.badoo.ribs.tutorials.tutorial3.util

/**
 * A very dumb interface for now.
 */
interface User {

    fun name(): String

    companion object {
        val DUMMY = object : User {
            override fun name(): String =
                "Tutorial User"
        }
    }
}
<file_sep>package com.badoo.ribs.portal

import com.badoo.ribs.annotation.ExperimentalApi
import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.modality.AncestryInfo
import io.reactivex.Single

@ExperimentalApi
interface RxPortal : Portal {

    fun showDefault(): Single<Rib>
    fun showInPortal(ancestryInfo: AncestryInfo): Single<Rib>
}
<file_sep>package com.badoo.ribs.tutorials.tutorial2.rib.hello_world

import com.badoo.ribs.clienthelper.interactor.Interactor
import com.badoo.ribs.core.modality.BuildParams

class HelloWorldInteractor(
    buildParams: BuildParams<Nothing?>
) : Interactor<HelloWorld, HelloWorldView>(
    buildParams = buildParams
)
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.option_selector.builder

import javax.inject.Scope

@Scope
@Retention(AnnotationRetention.RUNTIME)
internal annotation class OptionSelectorScope
<file_sep>package com.badoo.ribs.samples.transitionanimations.rib.parent

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.routing.transition.handler.TransitionHandler
import com.badoo.ribs.samples.transitionanimations.rib.parent.routing.ParentChildBuilders
import com.badoo.ribs.samples.transitionanimations.rib.parent.routing.ParentRouter
import com.badoo.ribs.samples.transitionanimations.rib.parent.routing.ParentRouter.Configuration
import com.badoo.ribs.samples.transitionanimations.rib.parent.routing.ParentTransitionHandler

class ParentBuilder : SimpleBuilder<Parent>() {

    private val builders = ParentChildBuilders()

    override fun build(buildParams: BuildParams<Nothing?>): Parent {
        val backStack = backStack(buildParams)
        val transitionHandler = ParentTransitionHandler()
        val router = router(buildParams, backStack, transitionHandler)
        val presenter = presenter(backStack)
        val viewDependency = object : ParentView.Dependency {
            override val presenter: ParentPresenter = presenter
        }

        return node(buildParams, viewDependency, router)
    }

    private fun backStack(buildParams: BuildParams<Nothing?>): BackStack<Configuration> =
        BackStack(
            buildParams = buildParams,
            initialConfiguration = Configuration.Child1
        )

    private fun router(
        buildParams: BuildParams<Nothing?>,
        backStack: BackStack<Configuration>,
        transitionHandler: TransitionHandler<Configuration>
    ): ParentRouter =
        ParentRouter(
            buildParams = buildParams,
            routingSource = backStack,
            builders = builders,
            transitionHandler = transitionHandler
        )

    private fun presenter(backStack: BackStack<Configuration>): ParentPresenterImpl =
        ParentPresenterImpl(backStack)

    private fun node(
        buildParams: BuildParams<Nothing?>,
        viewDependency: ParentView.Dependency,
        router: ParentRouter
    ) =
        ParentNode(
            buildParams = buildParams,
            viewFactory = ParentViewImpl.Factory().invoke(viewDependency),
            plugins = listOfNotNull(
                router
            )
        )
}
<file_sep>package com.badoo.ribs.samples.back_stack.rib.child_c

import com.badoo.ribs.core.Rib

interface ChildC : Rib {

    interface Dependency

}
<file_sep># Tutorial #4

## The goal of this tutorial

To cover how to implement proper `Input` / `Output` communication with child RIBs.


## Outline

Every RIB can potentially expose a set of `Inputs` and `Outputs` on its main interface.

This is the public API of the RIB, and every RIB guarantees that as long as the proper dependencies are supplied (i.e. a consumer of its `Output` that it can talk to / a source of its `Input` that it can observe), it will automatically wire the required setup internally.

Which is an amazing thing when you think about it:
1. When using a RIB, it's not necessary to look inside and understand its internals to make it work, it's enough to have a look at its public API
2. It's also not necessary to create *any* additional wiring or setup to make it work: if you can build it, it works!


## Outputs

There's one thing we left out so far: in **tutorial2** we removed the functionality of the **Say hello!** button, and we said we'd put it back later.

Now's the time!

The classes in this tutorial have all the removed pieces put back in:
- `HelloWorld` interface (`Output` and dependency for it)
- `HelloWorldModule` - uses `output` when constructing Interactor
- `HelloWorldInteractor` - constructor parameter, and using `output` in `onViewCreated`
- `ViewEventToOutput` in the `hello_world.mapper` package

This means that as far as `HelloWorld` RIB is concerned, when its button is pressed, it will correctly trigger `Output.HelloThere` on its output channel.

Now of course we need to provide the dependency for consuming this `Output` from `GreetingsContainer`.
 
This will be similar to what we did in the previous tutorial - we will satisfy it in the parent, directly. 

More specifically, as `Outputs` and `Inputs` are forms of communication with a RIB, they should be handled as part of the business logic. Which means, we will want to implement it in the parent `Interactor`.

Let's open `GreetingsContainerInteractor` and implement reacting to its child's `Output`. What to do with its `Output` actually? Well, `GreetingsContainer` has its own `Output` that it communicates to the outside world, so right now we will make it trigger just that.

```kotlin
// mind the correct imports:
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorld
import android.os.Bundle

class GreetingsContainerInteractor(
    savedInstanceState: Bundle?,
    router: Router<Configuration, Nothing, Configuration, Nothing, Nothing>,
    output: Consumer<GreetingsContainer.Output>
) : Interactor<Configuration, Configuration, Nothing, Nothing>(
    savedInstanceState = savedInstanceState,
    router = router
) {

    internal val helloWorldOutputConsumer: Consumer<HelloWorld.Output> = Consumer {
        when (it) {
            HelloThere -> output.accept(GreetingsSaid("Someone said hello!"))
        }
    }
}
```
 
Now let's tell Dagger to use this!

Scroll to the bottom of `GreetingsContainerModule` and replace the `TODO()` block you find there to the object we created:

```kotlin
@GreetingsContainerScope
@Provides
@JvmStatic
internal fun helloWorldOutputConsumer(
    interactor: GreetingsContainerInteractor
) : Consumer<HelloWorld.Output> =
    interactor.helloWorldOutputConsumer
```

"If you can give me `GreetingsContainerInteractor`, I know how to give you a `Consumer<HelloWorld.Output>`". 

And since `GreetingsContainerInteractor` was marked with `@GreetingsContainerScope` in the same DI configuration (check the method named `interactor`), it's treated as a singleton in the scope, making `Dagger` reuse the same exact instance of it when constructing this `Consumer`, instead of creating a new one. 

Now we've established a chain:

`HelloWorldView` =(`Event`)⇒ `ViewEventToOutput` =(`HelloWorld.Output`)⇒ `GreetingsContainerInteractor` =(`GreetingsContainer.Output`)⇒ `RootActivity`

The **tutorial4** app should build and run at this point, and display the Snackbar when the button is pressed.


## Inputs

Let's say we want to update the text on the button from outside of the RIB.

To expose this functionality, let's add an `Input` to `HelloWorld`:

```kotlin
interface HelloWorld : Rib {

    interface Dependency {
        fun helloWorldInput(): ObservableSource<Input> // add this
        fun helloWorldOutput(): Consumer<Output>
        // remainder omitted
    }

    // add this
    sealed class Input {
        data class UpdateButtonText(val text: Text): Input()
    }

    sealed class Output {
        object HelloThere : Output()
    }
    
    // remainder omitted
}    
```

Note that we added a new dependency, too, on `ObservableSource<Input>`. 
 
> Reminder – as we've established in **tutorial1**:
> - if a RIB has `Input`, then it also has a dependency of 
> `ObservableSource<Input>`
> - if a RIB has `Output`, then it also has a dependency of `Consumer<Output>`
> 
> This is so that we can implement the guaranteed auto-wiring functionality of the RIB, described in the **Outline** section at the beginning of this tutorial

Let's use this input - add it to the constructor of our `Interactor` inside `HelloWorldModule`:

```kotlin
@HelloWorldScope
@Provides
@JvmStatic
internal fun interactor(
    savedInstanceState: Bundle?,
    user: User,
    config: HelloWorld.Config,
    input: ObservableSource<HelloWorld.Input>, // add this
    output: Consumer<HelloWorld.Output>
): HelloWorldInteractor =
    HelloWorldInteractor(
        savedInstanceState = savedInstanceState,
        user = user,
        config = config,
        input = input, // add this
        output = output
    )
```

And:

```kotlin
class HelloWorldInteractor(
    savedInstanceState: Bundle?,
    private val user: User,
    private val config: HelloWorld.Config,
    private val input: ObservableSource<HelloWorld.Input>, // add this
    private val output: Consumer<HelloWorld.Output>
)
```

Now let's implement the functionality on the other end, before connecting the dots.

If we want to change the label of the button, we need to go to the view:

```kotlin
interface HelloWorldView : RibView,
    ObservableSource<Event>,
    Consumer<ViewModel> {

    sealed class Event {
        object ButtonClicked : Event()
    }

    data class ViewModel(
        val titleText: Text,
        val welcomeText: Text,
        val buttonText: Text // add this
    )
}
```

And in `HelloWorldViewImpl`:

```kotlin
override fun accept(vm: ViewModel) {
    title.text = vm.titleText.resolve(androidView.context)
    welcome.text = vm.welcomeText.resolve(androidView.context)
    button.text = vm.buttonText.resolve(androidView.context) // add this
}
```

Now we need to update providing a `ViewModel` from the `Interactor`.

We can easily modify the initial one by adding the last line: 

```kotlin
view.accept(
    HelloWorldView.ViewModel(
        titleText = Text.Resource(R.string.hello_world_title, user.name()),
        welcomeText = config.welcomeMessage,
        buttonText = Text.Resource(R.string.hello_world_button_text)
    )
)
```

But this will only create one, and never update it - and we want to do just that, whenever an `Input.UpdateButtonText` arrives.

We can use the `Binder` for that, similarly to how we already use it just a couple lines below the `ViewModel` creation:

```kotlin
viewLifecycle.startStop {
    bind(view to output using ViewEventToOutput)
}
```
   
To outline what we want to do, add a new line here:

```kotlin
viewLifecycle.startStop {
    bind(view to output using ViewEventToOutput)
    bind(input to view using InputToViewModel)
}
```

Create `InputToViewModel` in the `mapper` package:

```kotlin
package com.badoo.ribs.tutorials.tutorial4.rib.hello_world.mapper

import com.badoo.ribs.android.text.Text
import com.badoo.ribs.tutorials.tutorial4.R
import com.badoo.ribs.tutorials.tutorial4.rib.hello_world.HelloWorld
import com.badoo.ribs.tutorials.tutorial4.rib.hello_world.HelloWorld.Input.UpdateButtonText
import com.badoo.ribs.tutorials.tutorial4.rib.hello_world.HelloWorldView.ViewModel
import com.badoo.ribs.tutorials.tutorial4.util.User

class InputToViewModel(
    private val user: User,
    private val config: HelloWorld.Config
) : (HelloWorld.Input) -> ViewModel? {
    
    override fun invoke(input: HelloWorld.Input): ViewModel? =
        when (input) {
            is UpdateButtonText -> ViewModel(
                titleText = Text.Resource(R.string.hello_world_title, user.name()),
                welcomeText = config.welcomeMessage,
                buttonText = input.text // using the incoming data
            )
        }
}
```
      
Two things worth mentioning here:
1. Notice how the return type is nullable: `ViewModel?` – meaning we don't have to create one if we don't want to. A legitimate use-case is if in the future we decide to add more types of `Input`, and not all of them actually do something with the view – in those branches in the `when` expression we can just return `null`, and `Binder` will not propagate it to the view.

2. Notice that we actually needed to pass `user` and `config` here, so it can no longer be just a Kotlin object. Modify our `Interactor` to create it:

```kotlin
    // create the transformer
    private val inputToViewModel = InputToViewModel(user, config)

    override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
        super.onViewCreated(view, viewLifecycle)
        setInitialViewModel(view)
        viewLifecycle.startStop {
            bind(view to output using ViewEventToOutput)
            bind(input to view using inputToViewModel) // use it here
        }
    }

    // extracted this to a method too
    private fun setInitialViewModel(view: HelloWorldView) {
        view.accept(
            HelloWorldView.ViewModel(
                titleText = Text.Resource(R.string.hello_world_title, user.name()),
                welcomeText = config.welcomeMessage,
                buttonText = Text.Resource(R.string.hello_world_button_text)
            )
        )
    }
```

> Alternatively, we could create the transformer in our DI module and pass it in the constructor (then we could spare passing in `user` and `config`). Since we don't need it outside of the `Interactor`, and since it's just a simple mapper (it's not like we'd want to mock it), it's also ok to keep it here. Up to you I guess. 


## Test run

Right now, we only implemented the functionality to react to exposed `Input` type on the `HelloWorld` side of things.

Now we also need to satisfy the dependency of `ObservableSource<Input>` in the parent, which we will do similarly to how we did with `Output` before:

```kotlin

class GreetingsContainerInteractor(
    savedInstanceState: Bundle?,
    router: Router<Configuration, Nothing, Configuration, Nothing, Nothing>,
    output: Consumer<GreetingsContainer.Output>
) : Interactor<Configuration, Configuration, Nothing, Nothing>(
    savedInstanceState = savedInstanceState,
    router = router
) {
    // Add this:
    internal val helloWorldInputSource: Relay<HelloWorld.Input> = BehaviorRelay.create()

    internal val helloWorldOutputConsumer: Consumer<HelloWorld.Output> = Consumer {
        when (it) {
            HelloThere -> output.accept(GreetingsSaid("Someone said hello"))
        }
    }

    // Add this to send an Input immediately when this RIB attaches
    override fun onAttach(ribLifecycle: Lifecycle, savedInstanceState: Bundle?) {
        super.onAttach(ribLifecycle, savedInstanceState)
        helloWorldInputSource.accept(
            HelloWorld.Input.UpdateButtonText(
                Text.Plain("Woo hoo!")
            )
        )
    }
}
```

> Right now we will only provide an `Input` statically and once. We will make it more useful in the next tutorial.

And add it to the bottom of `GreetingsContainerModule` so that Dagger understands to grab this when providing `HelloWorld.Dependency`:

```kotlin
@GreetingsContainerScope
@Provides
@JvmStatic
internal fun helloWorldInputSource(
    interactor: GreetingsContainerInteractor
) : ObservableSource<HelloWorld.Input> =
    interactor.helloWorldInputSource
```

Now we've established a chain:

`GreetingsContainerInteractor` =(`HelloWorld.Input`)⇒`Relay`⇒`InputToViewModel` =(`ViewModel`)⇒ `HelloWorldView`

The app should now compile. Test it!

![](https://i.imgur.com/ISU6yZU.png)


## Tutorial complete
 
Congratulations! You can advance to the next one.


## Summary

**`Inputs` and `Outputs`**

- `Inputs` and `Outputs` are forms of communication with a RIB:
    - `Inputs` expose functionality that can be triggered from the outside
    - `Outputs` signal some events where deciding what to do lies beyond the responsibility of the RIB in question

- Paired dependencies: 
    - `Inputs` always come with a dependency of `ObservableSource<Input>`.
    - `Outputs` always come with a dependency of `Consumer<Output>`.
    - This is to ensure auto-wiring of the RIB, so that as long as you can build it, it works automatically.

- When satisfying `Output` and `Input` dependencies, we **always** want to do that directly in the parent:
    - By satisfying these dependencies, we create a connection between the place where we satisfy them and the RIB in question.
    - Having a certain RIB as a child is an implementation detail of the parent, that should be kept hidden to maintain flexibility.
    - If we were to bubble up the dependency to a higher level, we would expose this implementation detail. We would create a connection between the outside world and the implementation detail, making the immediate parent lose its ability to easily change it. This should be avoided at all costs.
    - If the parent cannot handle an `Output` message directly, it can transform it to its own `Output` type, keeping the implementation detail hidden.

<file_sep>package com.badoo.ribs.samples.buildtime.rib.build_time_deps

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.samples.buildtime.rib.build_time_deps.BuildTimeDepsRouter.Configuration
import com.badoo.ribs.samples.buildtime.rib.profile.ProfileBuilder

class BuildTimeDepsBuilder : SimpleBuilder<BuildTimeDeps>() {

    override fun build(buildParams: BuildParams<Nothing?>): BuildTimeDeps {
        val backStack: BackStack<Configuration> = BackStack(
            initialConfiguration = Configuration.Default,
            buildParams = buildParams
        )
        val presenter = BuildTimeDepsPresenterImpl(
            backStack = backStack
        )
        val router = BuildTimeDepsRouter(
            buildParams = buildParams,
            routingSource = backStack,
            profileBuilder = ProfileBuilder()
        )
        val viewDependencies = object : BuildTimeDepsView.Dependency {
            override val presenter: BuildTimeDepsPresenter = presenter
        }
        return BuildTimeDepsNode(
            buildParams = buildParams,
            viewFactory = BuildTimeDepsViewImpl.Factory().invoke(deps = viewDependencies),
            plugins = listOf(presenter, router)
        )
    }
}
<file_sep>// Use the .github folder as a way to find the root directory.
// Without this, the plugins module will not be able to use this script without hacks.
File rootDirectory = rootDir
while (!rootDirectory.listFiles().any { it.isDirectory() && it.name == ".github" }) {
    rootDirectory = rootDirectory.parentFile
}

def props = new Properties()
def propsFile = file("$rootDirectory/local.properties")

boolean useCompose = false
if (propsFile.exists()) {
    propsFile.withInputStream { props.load(it) }
    useCompose = Boolean.parseBoolean(props.getProperty("useCompose").toString())
}

if (hasProperty("useCompose")) {
    useCompose = Boolean.parseBoolean(getProperty("useCompose").toString())
}

ext.useCompose = useCompose
<file_sep>package com.badoo.ribs.samples.transitionanimations.app

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.modality.BuildContext.Companion.root
import com.badoo.ribs.samples.transitionanimations.R
import com.badoo.ribs.samples.transitionanimations.rib.parent.ParentBuilder

class RootActivity : RibActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_root)
        super.onCreate(savedInstanceState)
    }

    override val rootViewGroup: ViewGroup
        get() = findViewById(R.id.root)

    override fun createRib(savedInstanceState: Bundle?): Rib =
        ParentBuilder().build(buildContext = root(savedInstanceState))

}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2

import androidx.lifecycle.Lifecycle
import com.badoo.ribs.core.plugin.ViewAware

internal class SimpleRoutingChild2Presenter : ViewAware<SimpleRoutingChild2View> {

    override fun onViewCreated(view: SimpleRoutingChild2View, viewLifecycle: Lifecycle) {
        view.showInitialMessage()
    }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.builder

import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.GreetingsContainer
import dagger.BindsInstance

@GreetingsContainerScope
@dagger.Component(
    modules = [GreetingsContainerModule::class],
    dependencies = [GreetingsContainer.Dependency::class]
)
internal interface GreetingsContainerComponent {

    @dagger.Component.Factory
    interface Factory {
        fun create(
            dependency: GreetingsContainer.Dependency,
            @BindsInstance buildParams: BuildParams<Nothing?>
        ): GreetingsContainerComponent
    }

    fun node(): Node<Nothing>
}
<file_sep>package com.badoo.ribs.tutorials.tutorial1.rib.hello_world.builder

import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorld
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorld.Output
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorldInteractor
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorldView
import dagger.Provides
import io.reactivex.functions.Consumer

@dagger.Module
internal object HelloWorldModule {

    @HelloWorldScope
    @Provides
    @JvmStatic
    internal fun interactor(
        buildParams: BuildParams<Nothing?>,
        output: Consumer<Output>
    ): HelloWorldInteractor =
        HelloWorldInteractor(
            buildParams = buildParams,
            output = output
        )

    @HelloWorldScope
    @Provides
    @JvmStatic
    internal fun node(
        buildParams: BuildParams<Nothing?>,
        customisation: HelloWorld.Customisation,
        interactor: HelloWorldInteractor
    ) : Node<HelloWorldView> = Node(
        buildParams = buildParams,
        viewFactory = customisation.viewFactory(null),
        plugins = listOf(interactor)
    )
}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child2

import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.customisation.RibCustomisation

interface SimpleRoutingChild1Child2 : Rib {

    interface Dependency

    class Customisation(
        val viewFactory: SimpleRoutingChild1Child2View.Factory = SimpleRoutingChild1Child2ViewImpl.Factory()
    ) : RibCustomisation
}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1

import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.core.plugin.Plugin
import com.badoo.ribs.core.view.ViewFactory

internal class SimpleRoutingChild1Node(
    buildParams: BuildParams<*>,
    viewFactory: ViewFactory<SimpleRoutingChild1View>?,
    plugins: List<Plugin>
) : Node<SimpleRoutingChild1View>(
    buildParams = buildParams,
    viewFactory = viewFactory,
    plugins = plugins
), SimpleRoutingChild1
<file_sep># Tutorial #2

## The goal of this tutorial
To add a child RIB to another RIB


## Outline
Building on top of what we achieved in **tutorial1**, we have our `HelloWorld` RIB with its single button. 

So far, we directly attached it to our integration point, `RootActivity`, but now we want to see an example how to add it as a child RIB of another.


## Viewless RIBs

In the framework, individual RIBs are not forced to implement a view, they can also be purely business logic driven. In this case, they only add a `Node` to the `Node` tree, but no `View` to the View tree.

Of course, they can do everything else, and can have child RIBs all the same. If these children have their own `Views`, those will be attached directly to the `View` of the closest parent up in the tree that has one. 


## Starting out

Check the `com.badoo.ribs.tutorials.tutorial2.rib` package in the `tutorial2` module, you now see two RIBs: `HelloWorld`, and `GreetingsContainer`, the new one.
  
If you check `RootActivity`, it builds and attaches `GreetingsContainer` directly, and `HelloWorld` is not used anywhere (yet).

The project should compile without any changes, and if you launch the app, this is what you should see:

![Empty greetings container](https://i.imgur.com/4XmUpqF.png) 

Not too much to see here yet. `GreetingsContainer` is a viewless container RIB, so it does not add anything to the screen. We will add functionality to it in later tutorials, right now the only thing we want to do is to attach `HelloWorld` to it as its child.

To do that, first we need to familiarise ourselves with the concept of routing.


## Routing

Parent - child relationships and their dynamic changes are described under the concept of routing.

In case of Badoo RIBs, routing is done via these steps:
1. define the set of possible configurations a certain RIB can be in
2. define what a certain configuration means, by resolving it to a routing action
3. set the initial configuration for the Router
4. *(dynamically manipulate the Router to set it into any of the pre-defined configurations, based on business logic - in later tutorials)* 


## What is a configuration?

A configuration is just a label used to describe the routing state.

For example, imagine a `Root` RIB, which, on a very basic level, can have two possible subtrees attached: one if the user is logged out, and another if the user is logged in. But not both at the same time!

In this case, `LoggedOut` and `LoggedIn` would be the two possible configurations, which we could define as a Kotlin sealed class:

```kotlin
sealed class Configuration : Parcelable {
    @Parcelize object LoggedOut : Configuration()
    @Parcelize object LoggedIn : Configuration()
}
```

> ⚠️ Note how it implements `Parcelable`, so that the framework can save/restore our configurations later.

## What is a routing action?

Once we defined our possible configurations, we need to say *what* should happen when any of them is activated.

This is done by implementing the `resolveConfiguration` method in the `Router`:

```kotlin
abstract fun resolveConfiguration(configuration: C): RoutingAction
```

The library offers implementations of the `RoutingAction` interface, which should cover all cases you encounter.

For simplicity, we will now only look at one of them: the `AttachRibRoutingAction`, which, as the name implies, tells the `Router` to attach a certain child RIB.


## Getting our hands dirty
Let's have a look at routing for the `GreetingsContainer`:

```kotlin

class GreetingsContainerRouter(
    savedInstanceState: Bundle?
): Router</* ... */>(
    savedInstanceState = savedInstanceState,
    initialConfiguration = Configuration.Default
) {
    sealed class Configuration : Parcelable {
        @Parcelize object Default : Configuration()
    }

    override fun resolve(routing: RoutingElement<Configuration>): RoutingAction =
        RoutingAction.noop()
}
```

This looks pretty basic:
1. The possible set of configurations contain only one: `Default` 
2. `GreetingsContainerRouter` passes in `Configuration.Default` as `initialConfiguration` in the parent constructor. All `Routers` need to state their initial configurations. 
3. When resolving configurations, we always return `noop()` as a `RoutingAction`, doing nothing

Let's spice it up a bit!

First, rename `Default` to describe what we actually want to do:

```kotlin
sealed class Configuration : Parcelable {
    @Parcelize object HelloWorld : Configuration()
}
```

Next, change the resolution so that it attaches something:

```kotlin
import com.badoo.ribs.routing.action.AttachRibRoutingAction.Companion.attach

/* ... */

override fun resolve(routing: RoutingElement<Configuration>): RoutingAction =
    when (routing.configuration) {
        is Configuration.HelloWorld -> child { TODO() }
    }
```

> ⚠️ Notice how `AttachRibRoutingAction` also offers a convenience method `attach` to construct it, for which you can use static imports. The same goes for other `RoutingActions`, too.

> ⚠️ Also notice that even though `Configuration.HelloWorld` is a Kotlin object, there's still an `is` for it in the `when` expression. This is actually important, since after save/restore cycle, the instance restored from `Bundle` will be a different instance! Without using `is`, the `when` expression will not match in that case.

Alright, but what to put in place of the `TODO()` statement?

Looking at the signature of the `attach` method it needs a lambda that can build another `Node` given a nullable `Bundle` (representing `savedInstanceState`):

```kotlin
fun attach(builder: (Bundle?) -> Node<*>): RoutingAction
```

## We need a Builder

To build the `HelloWorld` RIB, we need an instance its Builder: `HelloWorldBuilder`, just as we did in **tutorial1**. So first just add it as a constructor dependency to our Router:

```kotlin
class GreetingsContainerRouter(
    private val helloWorldBuilder: HelloWorldBuilder
) /* rest is the same */
```

We'll care about how to pass it here just in a moment, but first let's finish our job here, and replace the `TODO()` block in our `attach` block:

```kotlin
override fun resolve(routing: RoutingElement<Configuration>): RoutingAction =
    when (routing.configuration) {
        is Configuration.HelloWorld -> child { helloWorldBuilder.build(it) }
    }
```

Our job is done here in the `GreetingsContainerRouter`, let's see how we can provide the Builder we need.


## The dependency chain

Badoo RIBs relies on Dagger2 to provide dependencies. In the `builder` subpackage, let's open `GreetingsContainerModule`, and notice how the first method constructs the Router:

```kotlin
@GreetingsContainerScope
@Provides
@JvmStatic
internal fun router(
    // pass component to child rib builders, or remove if there are none
    component: GreetingsContainerComponent
): GreetingsContainerRouter =
    GreetingsContainerRouter()
```

Notice how the constructor invocation has a compilation error now, since we don't yet pass in the required dependency. Let's do that, and change the constructor invocation to:

```kotlin
GreetingsContainerRouter(
    helloWorldBuilder = HelloWorldBuilder()
)
```

Now it's the `HelloWorldBuilder()` part that's missing something. If we open it up, we get a reminder that in order to construct it, we need to satisfy the `HelloWorld` RIB's dependencies. 

Notice how the function has an unused parameter we can use:

```kotlin
internal fun router(
    // pass component to child rib builders, or remove if there are none
    component: GreetingsContainerComponent
)
```

Let's do that!
   
```kotlin
GreetingsContainerRouter(
    helloWorldBuilder = HelloWorldBuilder(component)
)
```

Notice the compilation error again:

![](https://imgur.com/0Kk27dp.png)

That's fine and expected! We need to make the connection and say that `HelloWorldComponent` should actually provide those dependencies:

![](https://i.imgur.com/yyJklDY.png)

> For simplicity, we removed all actual dependencies from `HelloWorld.Dependency` interface, so that we can attach it as easily as possible.
>
> Don't worry, we will add it back in the next tutorial!


## Test run!

The application should now compile, and launching it, this is what we should see:

![](https://i.imgur.com/iuarP6N.png)

It's not very different from what we've seen in **tutorial1**, but now it's added as a child RIB of `GreetingsContainer`.

> Note that because we removed the `Consumer<Output>` dependency from `HelloWorld`, the button now doesn't do anything when pressed. We'll fix that soon! 


## 🎉 🎉 🎉 Congratulations  🎉 🎉 🎉
 
 You can advance to the next tutorial!


## Summary

Steps to add a child RIB:
1. Go to Router
    1. Define configuration
    2. Resolve configuration to `child { childBuilder.build(it) }` routing action
    3. Define `childBuilder` as a constructor dependency 
2. Go to Dagger module
    1. When constructing the `Router`, create and pass in an instance of the required child `Builder`
    2. Add `component` as a parameter to `Builder` constructor
    3. Let our component extend the required `Dependency` interface
    4. *(Actually satisfy those dependencies - coming up in next tutorial)*
<file_sep>package com.badoo.ribs.tutorials.tutorial1.app

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.core.Node
import com.badoo.ribs.tutorials.tutorial1.R

/** The tutorial app's single activity */
class RootActivity : RibActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_root)
        super.onCreate(savedInstanceState)
    }

    override val rootViewGroup: ViewGroup
        get() = findViewById(R.id.root)

    override fun createRib(savedInstanceState: Bundle?): Node<*> =
        TODO("Create HelloWorldBuilder, supply dependencies in constructor, return built Node")
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.hello_world

import androidx.lifecycle.Lifecycle
import com.badoo.mvicore.android.lifecycle.startStop
import com.badoo.mvicore.binder.using
import com.badoo.ribs.android.text.Text
import com.badoo.ribs.clienthelper.interactor.Interactor
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial5.R
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorld.Config
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorld.Input
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorld.Output
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorldView.Event
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorldView.ViewModel
import com.badoo.ribs.tutorials.tutorial5.util.User
import io.reactivex.ObservableSource
import io.reactivex.functions.Consumer

class HelloWorldInteractor(
    private val user: User,
    private val config: Config,
    private val input: ObservableSource<Input>,
    private val output: Consumer<Output>,
    buildParams: BuildParams<Nothing?>
) : Interactor<HelloWorld, HelloWorldView>(
    buildParams = buildParams
) {

    private var currentButtonText = config.buttonText
    private val initialViewModel = viewModel(currentButtonText)
    private val inputToViewModel = { input: Input ->
        when (input) {
            is Input.UpdateButtonText -> {
                viewModel(input.text.also { currentButtonText = it })
            }
        }
    }

    private fun viewModel(buttonText: Text): ViewModel {
        return ViewModel(
            titleText = Text.Resource(R.string.hello_world_title, user.name()),
            welcomeText = config.welcomeMessage,
            buttonText = buttonText
        )
    }

    private val viewEventToOutput = { event: Event ->
        when (event) {
            Event.HelloButtonClicked -> Output.HelloThere(currentButtonText)
        }
    }

    override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
        super.onViewCreated(view, viewLifecycle)
        view.accept(initialViewModel)
        viewLifecycle.startStop {
            bind(view to output using viewEventToOutput)
            bind(input to view using inputToViewModel)
        }
    }
}
<file_sep>package com.badoo.ribs.minimal.state

import io.reactivex.observers.TestObserver
import org.junit.Test

class StoreTest {
    @Test
    fun `store emits initial state on observe`() {
        val store = object : Store<Int>(0) { }

        val testObserver = TestObserver<Int>()
        store.observe { testObserver.onNext(it) }

        testObserver.assertValue(0)
    }

    @Test
    fun `store emits latest state on observe`() {
        val store = object : Store<Int>(0) {
            fun next() {
                emit(state + 1)
            }
        }

        val testObserver = TestObserver<Int>()
        store.next()
        store.observe { testObserver.onNext(it) }

        testObserver.assertValue(1)
    }

    @Test
    fun `observer receives state updates`() {
        val store = object : Store<Int>(0) {
            fun next() {
                emit(state + 1)
            }
        }

        val testObserver = TestObserver<Int>()
        store.observe { testObserver.onNext(it) }
        store.next()

        testObserver.assertValues(0, 1)
    }

    @Test
    fun `observer does not receive values after cancel`() {
        val store = object : Store<Int>(0) {
            fun next() {
                emit(state + 1)
            }
        }

        val testObserver = TestObserver<Int>()
        val cancellable = store.observe { testObserver.onNext(it) }
        cancellable.cancel()

        store.next()
        testObserver.assertValues(0)
    }

    @Test
    fun `async store can emit multiple values through events`() {
        val store = object : AsyncStore<Int, String>("") {
            override fun reduceEvent(event: Int, state: String): String =
                state + event

            fun trigger() {
                emitEvent(1)
                emitEvent(2)
            }
        }

        val testObserver = TestObserver<String>()
        store.observe { testObserver.onNext(it) }

        store.trigger()
        testObserver.assertValues("", "1", "12")
    }
}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_parent.routing

import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.SimpleRoutingChild1
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.builder.SimpleRoutingChild1Builder
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.SimpleRoutingChild2
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.builder.SimpleRoutingChild2Builder
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_parent.SimpleRoutingParent

internal open class SimpleRoutingParentChildBuilders(
    dependency: SimpleRoutingParent.Dependency
) {
    private val subtreeDeps = SubtreeDependency(dependency)

    val child1: SimpleRoutingChild1Builder = SimpleRoutingChild1Builder(subtreeDeps)
    val child2: SimpleRoutingChild2Builder = SimpleRoutingChild2Builder(subtreeDeps)

    class SubtreeDependency(
        dependency: SimpleRoutingParent.Dependency
    ) : SimpleRoutingParent.Dependency by dependency,
        SimpleRoutingChild1.Dependency, SimpleRoutingChild2.Dependency {
        override val title: String = "Child 1 title"
    }
}
<file_sep>package com.badoo.ribs

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.common.rib.test.activity.R
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.core.Rib

class RibTestActivity : RibActivity() {

    override val rootViewGroup: ViewGroup
        get() = findViewById(android.R.id.content)

    override fun createRib(savedInstanceState: Bundle?): Rib =
        ribFactory!!(this, savedInstanceState)

    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_root)
        super.onCreate(savedInstanceState)
    }

    companion object {
        var ribFactory: ((RibTestActivity, Bundle?) -> Rib)? = null
    }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial4.rib.greetings_container.builder

import javax.inject.Scope

@Scope
@Retention(AnnotationRetention.RUNTIME)
internal annotation class GreetingsContainerScope
<file_sep>package com.badoo.ribs.tutorials.tutorial2.app

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildContext.Companion.root
import com.badoo.ribs.tutorials.tutorial2.R
import com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.GreetingsContainer
import com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.builder.GreetingsContainerBuilder
import com.google.android.material.snackbar.Snackbar
import io.reactivex.functions.Consumer

/** The tutorial app's single activity */
class RootActivity : RibActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_root)
        super.onCreate(savedInstanceState)
    }

    override val rootViewGroup: ViewGroup
        get() = findViewById(R.id.root)

    override fun createRib(savedInstanceState: Bundle?): Node<*> =
        GreetingsContainerBuilder(
            object : GreetingsContainer.Dependency {
                override fun greetingsContainerOutput(): Consumer<GreetingsContainer.Output> =
                    Consumer { output ->
                        when (output) {
                            is GreetingsContainer.Output.GreetingsSaid -> {
                                Snackbar.make(rootViewGroup, output.greeting, Snackbar.LENGTH_SHORT).show()
                            }
                        }
                    }
            }
        ).build(
            buildContext = root(
                savedInstanceState = savedInstanceState
            )
        )
}
<file_sep>package com.badoo.ribs.sandbox.rib.compose_parent

import android.view.ViewGroup
import com.badoo.ribs.core.customisation.inflate
import com.badoo.ribs.core.view.AndroidRibView
import com.badoo.ribs.core.view.RibView
import com.badoo.ribs.core.view.ViewFactory
import com.badoo.ribs.core.view.ViewFactoryBuilder
import com.badoo.ribs.sandbox.compose.R

interface ComposeParentView : RibView {

    interface Factory : ViewFactoryBuilder<Nothing?, ComposeParentView>
}


class ComposeParentViewImpl private constructor(
    override val androidView: ViewGroup
) : AndroidRibView(),
    ComposeParentView {

    class Factory : ComposeParentView.Factory {
        override fun invoke(deps: Nothing?): ViewFactory<ComposeParentView> = ViewFactory {
            ComposeParentViewImpl(
                it.inflate(R.layout.rib_compose_parent)
            )
        }
    }
}
<file_sep>package com.badoo.ribs.core.lifecycle

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Lifecycle.State
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import org.junit.Assert.assertEquals
import org.junit.Test

class MinimumCombinedLifecycleTest {

    @Test
    fun `completely duplicates state of single lifecycle`() {
        val test = TestLifecycle()
        val combined = MinimumCombinedLifecycle(test.lifecycle)
        STATES.forEach {
            if (it != State.INITIALIZED) {
                test.state = it
            }
            assertEquals(it, combined.lifecycle.currentState)
        }
    }

    @Test
    fun `completely duplicates state of lifecycles with same state`() {
        val test1 = TestLifecycle()
        val test2 = TestLifecycle()
        val combined = MinimumCombinedLifecycle(test1.lifecycle, test2.lifecycle)
        STATES.forEach {
            if (it != State.INITIALIZED) {
                test1.state = it
                test2.state = it
            }
            assertEquals(it, combined.lifecycle.currentState)
        }
    }

    @Test
    fun `destroyed when one of lifecycles is destroyed`() {
        val test1 = TestLifecycle()
        test1.state = State.DESTROYED
        val test2 = TestLifecycle()
        val combined = MinimumCombinedLifecycle(test1.lifecycle, test2.lifecycle)
        assertEquals(State.DESTROYED, combined.lifecycle.currentState)
    }

    private class TestLifecycle : LifecycleOwner {
        private val registry = LifecycleRegistry(this)

        var state: State
            get() = registry.currentState
            set(value) {
                registry.currentState = value
            }

        override fun getLifecycle(): Lifecycle = registry

    }

    companion object {
        val STATES =
            listOf(State.INITIALIZED, State.CREATED, State.STARTED, State.RESUMED, State.DESTROYED)
    }

}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.builder

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.SimpleRoutingChild2
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.SimpleRoutingChild2Node
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.SimpleRoutingChild2Presenter

class SimpleRoutingChild2Builder(
    private val dependency: SimpleRoutingChild2.Dependency
) : SimpleBuilder<SimpleRoutingChild2>() {

    override fun build(buildParams: BuildParams<Nothing?>): SimpleRoutingChild2 {
        val customisation = buildParams.getOrDefault(SimpleRoutingChild2.Customisation())
        val presenter = SimpleRoutingChild2Presenter()

        return SimpleRoutingChild2Node(
            buildParams = buildParams,
            viewFactory = customisation.viewFactory(null),
            plugins = listOf(presenter)
        )
    }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.app

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildContext
import com.badoo.ribs.tutorials.tutorial5.R
import com.badoo.ribs.tutorials.tutorial5.rib.greetings_container.GreetingsContainer
import com.badoo.ribs.tutorials.tutorial5.rib.greetings_container.builder.GreetingsContainerBuilder
import com.badoo.ribs.tutorials.tutorial5.util.User
import com.google.android.material.snackbar.Snackbar
import io.reactivex.functions.Consumer

/** The tutorial app's single activity */
class RootActivity : RibActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_root)
        super.onCreate(savedInstanceState)
    }

    override val rootViewGroup: ViewGroup
        get() = findViewById(R.id.root)

    override fun createRib(savedInstanceState: Bundle?): Node<*> =
        GreetingsContainerBuilder(
            object : GreetingsContainer.Dependency {
                override fun user(): User =
                    User.DUMMY

                override fun greetingsContainerOutput(): Consumer<GreetingsContainer.Output> =
                    Consumer { output ->
                        when (output) {
                            is GreetingsContainer.Output.GreetingsSaid -> {
                                Snackbar.make(
                                    rootViewGroup,
                                    "${output.greeting.resolve(this@RootActivity)} to you as well!",
                                    Snackbar.LENGTH_SHORT
                                ).show()
                            }
                        }
                    }
            }
        ).build(BuildContext.root(savedInstanceState = savedInstanceState))
}
<file_sep>package com.badoo.ribs.tutorials.tutorial1.rib.hello_world

import androidx.annotation.LayoutRes
import android.view.ViewGroup
import android.widget.Button
import com.badoo.ribs.core.view.RibView
import com.badoo.ribs.core.view.ViewFactoryBuilder
import com.badoo.ribs.core.customisation.inflate
import com.badoo.ribs.core.view.AndroidRibView
import com.badoo.ribs.core.view.ViewFactory
import com.badoo.ribs.tutorials.tutorial1.R
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorldView.Event
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorldView.ViewModel
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.ObservableSource
import io.reactivex.functions.Consumer

interface HelloWorldView : RibView,
    ObservableSource<Event>,
    Consumer<ViewModel> {

    sealed class Event {
        object ButtonClicked : Event()
    }

    data class ViewModel(
        val i: Int = 0
    )

    interface Factory : ViewFactoryBuilder<Nothing?, HelloWorldView>
}

class HelloWorldViewImpl private constructor(
    override val androidView: ViewGroup,
    private val events: PublishRelay<Event> = PublishRelay.create()
) : AndroidRibView(),
    HelloWorldView,
    ObservableSource<Event> by events,
    Consumer<ViewModel> {

    class Factory(
        @LayoutRes private val layoutRes: Int = R.layout.rib_hello_world
    ) : HelloWorldView.Factory {
        override fun invoke(deps: Nothing?): ViewFactory<HelloWorldView> = ViewFactory {
            HelloWorldViewImpl(
                it.inflate(layoutRes)
            )
        }
    }

    private val button: Button = androidView.findViewById(R.id.hello_world_button)

    init {
        button.setOnClickListener { events.accept(Event.ButtonClicked) }
    }

    override fun accept(vm: ViewModel) {
    }
}
<file_sep>package com.badoo.ribs.samples.buildtime.rib.build_time_deps

import com.badoo.ribs.core.plugin.ViewAware
import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.routing.source.backstack.operation.replace
import com.badoo.ribs.samples.buildtime.rib.build_time_deps.BuildTimeDepsRouter.Configuration.ShowProfile

interface BuildTimeDepsPresenter {

    fun onBuildChildClicked(profileId: Int)
}

internal class BuildTimeDepsPresenterImpl(
    private val backStack: BackStack<BuildTimeDepsRouter.Configuration>
) : ViewAware<BuildTimeDepsView>, BuildTimeDepsPresenter {

    override fun onBuildChildClicked(profileId: Int) {
        backStack.replace(ShowProfile(profileId))
    }
}
<file_sep>package com.badoo.ribs.example.logged_out_container.routing

import com.badoo.ribs.example.logged_out_container.LoggedOutContainer
import com.badoo.ribs.example.welcome.Welcome
import com.badoo.ribs.example.welcome.WelcomeBuilder

internal class LoggedOutContainerChildBuilders(
    val dependency: LoggedOutContainer.Dependency
) {
    private val subtreeDeps = SubtreeDeps(dependency)

    val welcomeBuilder = WelcomeBuilder(subtreeDeps)

    private class SubtreeDeps(
        dependency: LoggedOutContainer.Dependency
    ) : LoggedOutContainer.Dependency by dependency,
        Welcome.Dependency
}
<file_sep>package com.badoo.ribs.routing.transition.progress

class MultiProgressEvaluator : ProgressEvaluator {

    private val evaluators = mutableListOf<ProgressEvaluator>()

    fun add(evaluator: ProgressEvaluator) {
        evaluators.add(evaluator)
    }

    override var progress: Float =
        evaluators.map { it.progress }.min() ?: 0f

    override fun isPending(): Boolean =
        evaluators.any { it.isPending() }
}
<file_sep>@file:SuppressWarnings("LongParameterList")

package com.badoo.ribs.samples.comms_nodes_1.rib.container

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.samples.comms_nodes_1.rib.container.routing.ContainerChildBuilders
import com.badoo.ribs.samples.comms_nodes_1.rib.container.routing.ContainerRouter
import com.badoo.ribs.samples.comms_nodes_1.rib.container.routing.ContainerRouter.Configuration
import com.badoo.ribs.samples.comms_nodes_1.rib.container.routing.ContainerRouter.Configuration.Content

class ContainerBuilder(
    private val dependency: Container.Dependency
) : SimpleBuilder<Container>() {

    private val builders by lazy { ContainerChildBuilders(dependency) }

    override fun build(buildParams: BuildParams<Nothing?>): Container {
        val backStack = backStack(buildParams)
        val router = router(buildParams, backStack, builders)
        val presenter = ContainerPresenterImpl(backStack)

        return ContainerNode(
            buildParams = buildParams,
            viewFactory = ContainerViewImpl.Factory().invoke(deps = null),
            plugins = listOfNotNull(router, presenter)
        )
    }

    private fun backStack(buildParams: BuildParams<Nothing?>): BackStack<Configuration> =
        BackStack(
            buildParams = buildParams,
            initialConfiguration = Content.Child1
        )

    private fun router(
        buildParams: BuildParams<Nothing?>,
        backStack: BackStack<Configuration>,
        builders: ContainerChildBuilders
    ) = ContainerRouter(
        buildParams = buildParams,
        builders = builders,
        routingSource = backStack
    )
}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1

import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.customisation.RibCustomisation

interface SimpleRoutingChild1Child1 : Rib {

    interface Dependency

    class Customisation(
        val viewFactory: SimpleRoutingChild1Child1View.Factory = SimpleRoutingChild1Child1ViewImpl.Factory()
    ) : RibCustomisation
}
<file_sep>package com.badoo.ribs.routing.transition.progress

import com.badoo.ribs.util.RIBs


class SingleProgressEvaluator : ProgressEvaluator {

    private sealed class Progress {
        object Initialised : Progress()
        object Reset : Progress()
        class InProgress : Progress() {
            var progress: Float = 0f
        }
        object Finished : Progress()
    }

    private var state: Progress =
        Progress.Initialised

    override val progress: Float =
        when (val state = state) {
            is Progress.Initialised -> 0f
            is Progress.Reset -> 0f
            is Progress.InProgress -> state.progress
            is Progress.Finished -> 1f
        }

    fun start() {
        state = Progress.InProgress()
    }

    fun updateProgress(progress: Float) {
        when (val state = state) {
            is Progress.InProgress -> state.progress = progress
            else -> if (progress != 1f && progress != 0f) {
                RIBs.errorHandler.handleNonFatalError(
                    "Progress $progress is not applicable to $state"
                )
            }
        }
    }

    fun reset() {
        state = Progress.Reset
    }

    fun markFinished() {
        state = Progress.Finished
    }

    override fun isPending(): Boolean =
        isInProgress() || isInitialised()

    private fun isInProgress(): Boolean =
        state is Progress.InProgress

    private fun isInitialised(): Boolean =
        state is Progress.Initialised
}
<file_sep>package com.badoo.ribs.samples.dialogs.app

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.android.dialog.AlertDialogLauncher
import com.badoo.ribs.android.dialog.DialogLauncher
import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.modality.BuildContext
import com.badoo.ribs.samples.dialogs.R
import com.badoo.ribs.samples.dialogs.rib.dialogs_example.DialogsExample
import com.badoo.ribs.samples.dialogs.rib.dialogs_example.DialogsBuilder

class DialogsActivity : RibActivity() {

    private val deps = object : DialogsExample.Dependency {
        override val dialogLauncher: DialogLauncher = AlertDialogLauncher(
            this@DialogsActivity,
            this@DialogsActivity.lifecycle
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_dialogs)
        super.onCreate(savedInstanceState)
    }

    override val rootViewGroup: ViewGroup
        get() = findViewById(R.id.dialogs_activity_root)

    override fun createRib(savedInstanceState: Bundle?): Rib =
        DialogsBuilder(deps).build(BuildContext.root(savedInstanceState))
}

<file_sep>package com.badoo.ribs.tutorials.tutorial3.rib.hello_world.builder

import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial3.rib.hello_world.HelloWorld
import com.badoo.ribs.tutorials.tutorial3.rib.hello_world.HelloWorldInteractor
import com.badoo.ribs.tutorials.tutorial3.rib.hello_world.HelloWorldView
import dagger.Provides

@dagger.Module
internal object HelloWorldModule {

    @HelloWorldScope
    @Provides
    @JvmStatic
    internal fun interactor(
        buildParams: BuildParams<Nothing?>
    ): HelloWorldInteractor =
        HelloWorldInteractor(
            buildParams = buildParams
        )

    @HelloWorldScope
    @Provides
    @JvmStatic
    internal fun node(
        buildParams: BuildParams<Nothing?>,
        customisation: HelloWorld.Customisation,
        interactor: HelloWorldInteractor
    ) : Node<HelloWorldView> = Node(
        buildParams = buildParams,
        viewFactory = customisation.viewFactory(null),
        plugins = listOf(interactor)
    )
}
<file_sep>package com.badoo.ribs.routing.state.feature

import com.badoo.ribs.routing.state.RoutingContext
import com.badoo.ribs.routing.state.changeset.TransitionDescriptor
import com.badoo.ribs.routing.state.feature.state.WorkingState
import com.badoo.ribs.routing.transition.handler.TransitionHandler
import com.badoo.ribs.test.TestConfiguration
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import org.junit.Test

class ActorTest {

    private val pendingTransition: PendingTransition<TestConfiguration> = mock()
    private val pendingTransitionFactory: PendingTransitionFactory<TestConfiguration> = mock {
        on { create(any(), any(), any(), any()) } doReturn pendingTransition
    }

    /**
     * [Case-1]
     *  1. there is no ongoingTransitions
     *  2. there is no pendingTransitions
     *  3. globalActivationState is NOT SLEEPING
     *  4. transitionHandler is NOT null
     */
    @Test
    fun `GIVEN Case-1 conditions WHEN a RoutingChange is accepted THEN a pendingTransition is scheduled`() {
        val state = WorkingState<TestConfiguration>(activationLevel = RoutingContext.ActivationState.ACTIVE)
        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = mock()
        )

        getActor().invoke(state, transaction)

        verify(pendingTransition).schedule()
    }

    /**
     * [Case-2]
     *  1. there is no ongoingTransitions
     *  2. there is no pendingTransitions
     *  2. globalActivationState is SLEEPING
     *  3. transitionHandler is NOT null
     */
    @Test
    fun `GIVEN Case-2 conditions WHEN a RoutingChange is accepted THEN a pendingTransition is NOT scheduled`() {
        val state = WorkingState<TestConfiguration>(activationLevel = RoutingContext.ActivationState.SLEEPING)
        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = mock()
        )

        getActor().invoke(state, transaction)

        verify(pendingTransition, never()).schedule()
    }

    /**
     * [Case-3]
     *  1. there is no ongoingTransitions
     *  2. there is no pendingTransitions
     *  3. globalActivationState is NOT SLEEPING
     *  4. transitionHandler is null
     */
    @Test
    fun `GIVEN Case-3 conditions WHEN a RoutingChange is accepted THEN a pendingTransition is NOT scheduled`() {
        val state = WorkingState<TestConfiguration>(activationLevel = RoutingContext.ActivationState.ACTIVE)
        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = mock()
        )

        getActor(null).invoke(state, transaction)

        verify(pendingTransition, never()).schedule()
    }

    /**
     * [Case-4]
     *  1. there is no ongoingTransitions
     *  2. there is 1 pendingTransitions
     *  3. globalActivationState is NOT SLEEPING
     *  4. transitionHandler is NOT null
     *  5. The existing pending transition is the reversed of the new RoutingChange
     */
    @Test
    fun `GIVEN Case-4 conditions WHEN a RoutingChange is accepted THEN a the old pending transition is discarded, and new one is scheduled`() {
        val oldPendingTransition: PendingTransition<TestConfiguration> = mock {
            on { descriptor } doReturn mock()
        }
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            pendingTransitions = listOf(oldPendingTransition)
        )
        val newDescriptor: TransitionDescriptor = mock {
            on { isReverseOf(oldPendingTransition.descriptor) } doReturn true
        }
        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = newDescriptor
        )

        getActor().invoke(state, transaction)

        verify(oldPendingTransition).discard()
        verify(pendingTransition).schedule()
    }

    /**
     * [Case-5]
     *  1. there is no ongoingTransitions
     *  2. there is 1 pendingTransitions
     *  3. globalActivationState is NOT SLEEPING
     *  4. transitionHandler is NOT null
     *  5. The existing pending transition is the continuation of the new RoutingChange
     */
    @Test
    fun `GIVEN Case-5 conditions WHEN a RoutingChange is accepted THEN the old pending transition is completeWithoutTransition, and new one is scheduled`() {
        val oldPendingTransition: PendingTransition<TestConfiguration> = mock {
            on { descriptor } doReturn mock()
        }
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            pendingTransitions = listOf(oldPendingTransition)
        )
        val newDescriptor: TransitionDescriptor = mock {
            on { isReverseOf(oldPendingTransition.descriptor) } doReturn false
            on { isContinuationOf(oldPendingTransition.descriptor) } doReturn true
        }
        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = newDescriptor
        )

        getActor().invoke(state, transaction)

        verify(oldPendingTransition).completeWithoutTransition()
        verify(pendingTransition).schedule()
    }

    /**
     * [Case-6]
     *  1. there is no ongoingTransitions
     *  2. there is no pendingTransitions
     *  3. globalActivationState is NOT SLEEPING
     *  4. transitionHandler is NOT null
     *  5. The existing pending transition is the continuation of the new RoutingChange
     */
    @Test
    fun `GIVEN Case-6 conditions WHEN a RoutingChange is accepted THEN the old pending transition is completeWithoutTransition, and new one is scheduled`() {
        val oldPendingTransition: PendingTransition<TestConfiguration> = mock {
            on { descriptor } doReturn mock()
        }
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            pendingTransitions = listOf(oldPendingTransition)
        )
        val newDescriptor: TransitionDescriptor = mock {
            on { isReverseOf(oldPendingTransition.descriptor) } doReturn false
            on { isContinuationOf(oldPendingTransition.descriptor) } doReturn true
        }
        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = newDescriptor
        )

        getActor().invoke(state, transaction)

        verify(oldPendingTransition).completeWithoutTransition()
        verify(pendingTransition).schedule()
    }

    /**
     * [Case-7]
     *  1. there is no ongoingTransitions
     *  2. there is no pendingTransitions
     *  3. globalActivationState is NOT SLEEPING
     *  4. transitionHandler is NOT null
     *  5. The existing pending transition has no interactions with the new one
     */
    @Test
    fun `GIVEN Case-7 conditions WHEN a RoutingChange is accepted THEN then new one is scheduled and no actions are applied to old one`() {
        val oldPendingTransition: PendingTransition<TestConfiguration> = mock {
            on { descriptor } doReturn mock()
        }
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            pendingTransitions = listOf(oldPendingTransition)
        )
        val newDescriptor: TransitionDescriptor = mock {
            on { isReverseOf(oldPendingTransition.descriptor) } doReturn false
            on { isContinuationOf(oldPendingTransition.descriptor) } doReturn false
        }
        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = newDescriptor
        )

        getActor().invoke(state, transaction)

        verify(oldPendingTransition, never()).completeWithoutTransition()
        verify(oldPendingTransition, never()).discard()
        verify(pendingTransition).schedule()
    }

    /**
     * [Case-8]
     *  1. globalActivationState is NOT SLEEPING
     *  2. there is 1 pendingTransitions
     *  3. transitionHandler is NOT null
     *  4. Accepted transition is the pending one.
     */
    @Test
    fun `GIVEN Case-8 conditions WHEN a ExecutePendingTransition is accepted THEN then the pendingTransition is executed and ongoingTransition started`() {
        val ongoingTransition: OngoingTransition<TestConfiguration> = mock()
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            pendingTransitions = listOf(pendingTransition)
        )
        whenever(pendingTransition.execute(any())).thenReturn(ongoingTransition)

        getActor().invoke(state, Transaction.InternalTransaction.ExecutePendingTransition(pendingTransition))

        verify(pendingTransition).execute(any())
        verify(ongoingTransition).start()
    }

    /**
     * [Case-9]
     *  1. globalActivationState is NOT SLEEPING
     *  2. there is 1 pendingTransitions
     *  3. transitionHandler is NOT null
     *  4. Accepted transition is NOT the pending one.
     */
    @Test
    fun `GIVEN Case-9 conditions WHEN a ExecutePendingTransition is accepted THEN then the pendingTransition is discarded`() {
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            pendingTransitions = listOf<PendingTransition<TestConfiguration>>(mock())
        )

        getActor().invoke(state, Transaction.InternalTransaction.ExecutePendingTransition(pendingTransition))

        verify(pendingTransition).discard()
    }

    /**
     * [Case-10]
     *  1. globalActivationState is SLEEPING
     *  2. there is 1 pendingTransitions
     *  3. transitionHandler is NOT null
     *  4. Accepted transition is the pending one.
     */
    @Test
    fun `GIVEN Case-10 conditions WHEN a ExecutePendingTransition is accepted THEN then the pendingTransition is completedWithoutTransition`() {
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.SLEEPING,
            pendingTransitions = listOf(pendingTransition)
        )

        getActor().invoke(state, Transaction.InternalTransaction.ExecutePendingTransition(pendingTransition))

        verify(pendingTransition).completeWithoutTransition()
    }

    /**
     * [Case-11]
     *  1. globalActivationState is NOT SLEEPING
     *  2. there is 1 pendingTransitions
     *  3. transitionHandler is null
     *  4. Accepted transition is the pending one.
     */
    @Test
    fun `GIVEN Case-11 conditions WHEN a ExecutePendingTransition is accepted THEN then the pendingTransition is completedWithoutTransition`() {
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            pendingTransitions = listOf(pendingTransition)
        )

        getActor(null).invoke(state, Transaction.InternalTransaction.ExecutePendingTransition(pendingTransition))

        verify(pendingTransition).completeWithoutTransition()
    }

    /**
     * [Case-12]
     *  1. globalActivationState is NOT SLEEPING
     *  2. transitionHandler is NOT null
     *  3. there is no pendingTransitions
     *  4. there is 1 ongoingTransition
     *  4. The existing ongoingTransition is the reverse of the new one.
     */
    @Test
    fun `GIVEN Case-12 conditions WHEN a RoutingChange is accepted THEN then the old one is reversed, and new one is NOT scheduled`() {
        val ongoingTransition: OngoingTransition<TestConfiguration> = mock {
            on { descriptor } doReturn mock()
        }
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            ongoingTransitions = listOf(ongoingTransition)
        )
        val newDescriptor: TransitionDescriptor = mock {
            on { isReverseOf(ongoingTransition.descriptor) } doReturn true
        }

        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = newDescriptor
        )

        getActor().invoke(state, transaction)

        verify(ongoingTransition).reverse()
        verify(pendingTransition, never()).schedule()
    }

    /**
     * [Case-13]
     *  1. globalActivationState is NOT SLEEPING
     *  2. transitionHandler is NOT null
     *  3. there is no pendingTransitions
     *  4. there is 1 ongoingTransition
     *  4. The new transition is the continuation of the ongoing one.
     */
    @Test
    fun `GIVEN Case-13 conditions WHEN a RoutingChange is accepted THEN then the old one is jumpToEnd, and new one is scheduled`() {
        val ongoingTransition: OngoingTransition<TestConfiguration> = mock {
            on { descriptor } doReturn mock()
        }
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            ongoingTransitions = listOf(ongoingTransition)
        )
        val newDescriptor: TransitionDescriptor = mock {
            on { isContinuationOf(ongoingTransition.descriptor) } doReturn true
        }

        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = newDescriptor
        )

        getActor().invoke(state, transaction)

        verify(ongoingTransition).jumpToEnd()
        verify(pendingTransition).schedule()
    }

    /**
     * [Case-14]
     *  1. globalActivationState is NOT SLEEPING
     *  2. transitionHandler is NOT null
     *  3. there is no pendingTransitions
     *  4. there is 1 ongoingTransition
     *  4. The new transition has no interaction with ongoing one
     */
    @Test
    fun `GIVEN Case-14 conditions WHEN a RoutingChange is accepted THEN then new one is scheduled and no actions are applied to old one`() {
        val ongoingTransition: OngoingTransition<TestConfiguration> = mock {
            on { descriptor } doReturn mock()
        }
        val state = WorkingState(
            activationLevel = RoutingContext.ActivationState.ACTIVE,
            ongoingTransitions = listOf(ongoingTransition)
        )
        val newDescriptor: TransitionDescriptor = mock {
            on { isReverseOf(ongoingTransition.descriptor) } doReturn false
            on { isContinuationOf(ongoingTransition.descriptor) } doReturn false

        }

        val transaction = Transaction.RoutingChange<TestConfiguration>(
            changeset = emptyList(),
            descriptor = newDescriptor
        )

        getActor().invoke(state, transaction)

        verify(ongoingTransition, never()).reverse()
        verify(ongoingTransition, never()).jumpToEnd()
        verify(pendingTransition).schedule()
    }


    private fun getActor(transitionHandler: TransitionHandler<TestConfiguration>? = mock()) = Actor(
        resolver = mock(),
        activator = mock(),
        parentNode = mock(),
        transitionHandler = transitionHandler,
        effectEmitter = mock(),
        pendingTransitionFactory = pendingTransitionFactory
    )
}
<file_sep>package com.badoo.ribs.samples.back_stack.rib.parent.routing

import com.badoo.ribs.samples.back_stack.rib.child_a.ChildA
import com.badoo.ribs.samples.back_stack.rib.child_a.ChildABuilder
import com.badoo.ribs.samples.back_stack.rib.child_b.ChildB
import com.badoo.ribs.samples.back_stack.rib.child_b.ChildBBuilder
import com.badoo.ribs.samples.back_stack.rib.child_c.ChildC
import com.badoo.ribs.samples.back_stack.rib.child_c.ChildCBuilder
import com.badoo.ribs.samples.back_stack.rib.child_d.ChildD
import com.badoo.ribs.samples.back_stack.rib.child_d.ChildDBuilder
import com.badoo.ribs.samples.back_stack.rib.child_e.ChildE
import com.badoo.ribs.samples.back_stack.rib.child_e.ChildEBuilder
import com.badoo.ribs.samples.back_stack.rib.child_f.ChildF
import com.badoo.ribs.samples.back_stack.rib.child_f.ChildFBuilder
import com.badoo.ribs.samples.back_stack.rib.parent.Parent

internal open class ParentChildBuilders(
    dependency: Parent.Dependency
) {

    private val subtreeDeps = SubtreeDependency(dependency)

    val childA: ChildABuilder = ChildABuilder(subtreeDeps)
    val childB: ChildBBuilder = ChildBBuilder(subtreeDeps)
    val childC: ChildCBuilder = ChildCBuilder(subtreeDeps)
    val childD: ChildDBuilder = ChildDBuilder(subtreeDeps)
    val childE: ChildEBuilder = ChildEBuilder(subtreeDeps)
    val childF: ChildFBuilder = ChildFBuilder(subtreeDeps)

    class SubtreeDependency(
        dependency: Parent.Dependency
    ) : Parent.Dependency by dependency,
        ChildA.Dependency,
        ChildD.Dependency,
        ChildB.Dependency,
        ChildC.Dependency,
        ChildE.Dependency,
        ChildF.Dependency

}

<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.builder

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.SimpleRoutingChild1Child1
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.SimpleRoutingChild1Child1Node

class SimpleRoutingChild1Child1Builder(
    private val dependency: SimpleRoutingChild1Child1.Dependency
) : SimpleBuilder<SimpleRoutingChild1Child1>() {

    override fun build(buildParams: BuildParams<Nothing?>): SimpleRoutingChild1Child1 {
        val customisation = buildParams.getOrDefault(SimpleRoutingChild1Child1.Customisation())
        return SimpleRoutingChild1Child1Node(
            buildParams = buildParams,
            viewFactory = customisation.viewFactory(null)
        )
    }
}
<file_sep>package com.badoo.ribs.portal

import com.badoo.ribs.annotation.ExperimentalApi
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.portal.PortalRouter.Configuration
import com.badoo.ribs.routing.source.backstack.BackStack

@ExperimentalApi
class RxPortalBuilder(
    dependency: Portal.Dependency
) : BasePortalBuilder<RxPortal>(dependency) {

    override fun createNode(
        buildParams: BuildParams<Nothing?>,
        interactor: PortalInteractor,
        router: PortalRouter,
        backStack: BackStack<Configuration>
    ): RxPortal =
        RxPortalNode(
            buildParams = buildParams,
            backStack = backStack,
            plugins = listOf(
                interactor,
                router
            )
        )
}
<file_sep>package com.badoo.ribs.template.node.foo_bar

import androidx.lifecycle.Lifecycle
import com.badoo.mvicore.android.lifecycle.createDestroy
import com.badoo.mvicore.android.lifecycle.startStop
import com.badoo.mvicore.binder.using
import com.badoo.ribs.clienthelper.interactor.Interactor
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.template.node.foo_bar.analytics.FooBarAnalytics
import com.badoo.ribs.template.node.foo_bar.feature.FooBarFeature
import com.badoo.ribs.template.node.foo_bar.mapper.InputToWish
import com.badoo.ribs.template.node.foo_bar.mapper.NewsToOutput
import com.badoo.ribs.template.node.foo_bar.mapper.StateToViewModel
import com.badoo.ribs.template.node.foo_bar.mapper.ViewEventToAnalyticsEvent
import com.badoo.ribs.template.node.foo_bar.mapper.ViewEventToWish
import com.badoo.ribs.template.node.foo_bar.routing.FooBarRouter.Configuration

internal class FooBarInteractor(
    buildParams: BuildParams<*>,
    private val backStack: BackStack<Configuration>,
    private val feature: FooBarFeature
) : Interactor<FooBar, FooBarView>(
    buildParams = buildParams
) {

    override fun onCreate(nodeLifecycle: Lifecycle) {
        nodeLifecycle.createDestroy {
            bind(feature.news to rib.output using NewsToOutput)
            bind(rib.input to feature using InputToWish)
        }
    }

    override fun onViewCreated(view: FooBarView, viewLifecycle: Lifecycle) {
        viewLifecycle.startStop {
            bind(feature to view using StateToViewModel)
            bind(view to feature using ViewEventToWish)
            bind(view to FooBarAnalytics using ViewEventToAnalyticsEvent)
        }
    }

    override fun onChildBuilt(child: Node<*>) {
        /**
         * TODO bind children here and delete this comment block.
         *
         *  At this point children haven't set their own bindings yet,
         *  so it's safe to setup listening to their output before they start emitting.
         *
         *  On the other hand, they're not ready to receive inputs yet. Usually this is alright.
         *  If it's a requirement though, create those bindings in [onChildAttached]
         */
        // child.lifecycle.createDestroy {
            // when (child) {
                // is Child1 -> bind(child.output to someConsumer)
            // }
        // }
    }
}
<file_sep># Tutorial #3 - solutions

### HelloWorldView
```kotlin
interface HelloWorldView : RibView,
    ObservableSource<Event>,
    Consumer<ViewModel> {

    sealed class Event {
        object ButtonClicked : Event()
    }

    data class ViewModel(
        val titleText: Text,
        val welcomeText: Text
    )
}
```

### HelloWorldViewImpl
```kotlin
class HelloWorldViewImpl private constructor(
    context: Context, attrs: AttributeSet? = null, defStyle: Int = 0, private val events: PublishRelay<Event>
) : ConstraintLayout(context, attrs, defStyle),
    HelloWorldView,
    ObservableSource<Event> by events,
    Consumer<ViewModel> {

    @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyle: Int = 0
    ) : this(context, attrs, defStyle, PublishRelay.create<Event>())

    override val androidView = this
    private val title: TextView by lazy { findViewById<TextView>(R.id.hello_world_title) }
    private val welcome: TextView by lazy { findViewById<TextView>(R.id.hello_world_welcome) }
    private val button: Button by lazy { findViewById<Button>(R.id.hello_world_button) }

    override fun onFinishInflate() {
        super.onFinishInflate()
        button.setOnClickListener { events.accept(Event.ButtonClicked) }
    }

    override fun accept(vm: ViewModel) {
        title.text = vm.titleText.resolve(context)
        welcome.text = vm.welcomeText.resolve(context)
    }
}
```

### HelloWorldInteractor
```kotlin
class HelloWorldInteractor(
    user: User,
    config: HelloWorld.Configuration,
    savedInstanceState: Bundle?,
    router: Router<Configuration, Nothing, Content, Nothing, HelloWorldView>
) : Interactor<Configuration, Content, Nothing, HelloWorldView>(
    savedInstanceState = savedInstanceState,
    router = router
) {

    override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
        super.onViewCreated(view, viewLifecycle)
        view.accept(initialViewModel)
    }
    
    private val initialViewModel =
        HelloWorldView.ViewModel(
            titleText = Text.Resource(R.string.hello_world_title, user.name()),
            welcomeText = config.welcomeMessage
        )
}
```

### HelloWorld
```kotlin
interface HelloWorld : Rib {

    interface Dependency {
        fun user(): User
        fun config(): Config
    }

    data class Config {
        val welcomeMessage: Text
    }

    // remainder the same
}
```

### HelloWorldModule
```kotlin
@dagger.Module
internal object HelloWorldModule {

    @HelloWorldScope
    @Provides
    @JvmStatic
    internal fun interactor(
        user: User,
        config: HelloWorld.Configuration,
        router: HelloWorldRouter
    ): HelloWorldInteractor =
        HelloWorldInteractor(
            user = user,
            config = config,
            router = router
        )

    // remainder the same
}
```

### GreetingsContainer
```kotlin
interface GreetingsContainer : Rib {

    interface Dependency {
        fun user(): User
        fun greetingsContainerOutput(): Consumer<Output>
    }

    sealed class Output {
        data class GreetingsSaid(val greeting: String) : Output()
    }
}
```

### RootActivity
```kotlin
/** The tutorial app's single activity */
class RootActivity : RibActivity() {

    // remainder the same

    override fun createRib(savedInstanceState: Bundle?): Node<*> =
        GreetingsContainerBuilder(
            object : GreetingsContainer.Dependency {
                override fun user(): User =
                    User.DUMMY

                override fun greetingsContainerOutput(): Consumer<GreetingsContainer.Output> =
                    Consumer { output ->
                        when (output) {
                            is GreetingsContainer.Output.GreetingsSaid -> {
                                Snackbar.make(rootViewGroup, output.greeting, Snackbar.LENGTH_SHORT).show()
                            }
                        }
                    }
            }
        ).build()
}
```

<file_sep>package com.badoo.ribs.example

import android.os.Bundle
import android.preference.PreferenceManager
import android.view.ViewGroup
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.modality.BuildContext
import com.badoo.ribs.core.modality.BuildContext.Companion.root
import com.badoo.ribs.core.plugin.Plugin
import com.badoo.ribs.core.plugin.utils.debug.DebugControlsHost
import com.badoo.ribs.core.plugin.utils.debug.GrowthDirection
import com.badoo.ribs.debug.TreePrinter
import com.badoo.ribs.example.auth.AuthStateStorage
import com.badoo.ribs.example.auth.PreferencesAuthStateStorage
import com.badoo.ribs.example.network.ApiFactory
import com.badoo.ribs.example.network.NetworkError
import com.badoo.ribs.example.network.UnsplashApi
import com.badoo.ribs.example.root.Root
import com.badoo.ribs.example.root.RootBuilder
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers

class RootActivity : RibActivity() {

    private val networkErrorsRelay = PublishRelay.create<NetworkError>()
    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.example_activity_root)
        super.onCreate(savedInstanceState)
    }

    override val rootViewGroup: ViewGroup
        get() = findViewById(R.id.root)

    override fun createRib(savedInstanceState: Bundle?): Rib =
        buildRootNode(root(
            savedInstanceState = savedInstanceState,
            defaultPlugins = { node ->
                if (BuildConfig.DEBUG) {
                    listOfNotNull(
                        if (node.isRoot) createDebugControlHost() else null
                    )
                } else emptyList()
            }
        ))


    private fun buildRootNode(
        buildContext: BuildContext
    ): Root =
        RootBuilder(
            object : Root.Dependency {
                override val api: UnsplashApi = api()
                override val authStateStorage: AuthStateStorage =
                    PreferencesAuthStateStorage(PreferenceManager.getDefaultSharedPreferences(this@RootActivity))
                override val networkErrors: Observable<NetworkError> =
                    networkErrorsRelay
                        .observeOn(AndroidSchedulers.mainThread())
            }
        ).build(buildContext)

    private fun api(): UnsplashApi =
        ApiFactory.api(
            isDebug = BuildConfig.DEBUG,
            accessKey = BuildConfig.ACCESS_KEY,
            networkErrorConsumer = networkErrorsRelay
        )


    private fun createDebugControlHost(): Plugin =
        DebugControlsHost(
            viewGroupForChildren = { findViewById(R.id.debug_controls_host) },
            growthDirection = GrowthDirection.BOTTOM,
            defaultTreePrinterFormat = TreePrinter.FORMAT_SIMPLE
        )

}
<file_sep>package com.badoo.ribs.tutorials.tutorial4.rib.greetings_container.builder

import com.badoo.ribs.android.text.Text
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial4.R
import com.badoo.ribs.tutorials.tutorial4.rib.greetings_container.GreetingsContainerInteractor
import com.badoo.ribs.tutorials.tutorial4.rib.greetings_container.GreetingsContainerRouter
import com.badoo.ribs.tutorials.tutorial4.rib.hello_world.HelloWorld
import com.badoo.ribs.tutorials.tutorial4.rib.hello_world.builder.HelloWorldBuilder
import dagger.Provides
import io.reactivex.functions.Consumer

@dagger.Module
internal object GreetingsContainerModule {

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun router(
        // pass component to child rib builders, or remove if there are none
        component: GreetingsContainerComponent,
        interactor: GreetingsContainerInteractor,
        buildParams: BuildParams<Nothing?>
    ): GreetingsContainerRouter =
        GreetingsContainerRouter(
            buildParams = buildParams,
            routingSource = interactor,
            helloWorldBuilder = HelloWorldBuilder(component)
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun interactor(
        buildParams: BuildParams<Nothing?>
    ): GreetingsContainerInteractor =
        GreetingsContainerInteractor(
            buildParams = buildParams
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun node(
        buildParams: BuildParams<Nothing?>,
        router: GreetingsContainerRouter,
        interactor: GreetingsContainerInteractor
    ) : Node<Nothing> = Node<Nothing>(
        buildParams = buildParams,
        viewFactory = null,
        plugins = listOf(interactor, router)
    )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun helloWorldConfig(): HelloWorld.Config =
        HelloWorld.Config(
            welcomeMessage = Text.Resource(
                R.string.hello_world_welcome_text
            )
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun helloWorldOutputConsumer(
        interactor: GreetingsContainerInteractor
    ) : Consumer<HelloWorld.Output> =
        TODO("Pass instance from interactor")
}
<file_sep>package com.badoo.ribs.tutorials.tutorial1.rib.hello_world.mapper

import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorld
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorld.Output
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.HelloWorldView

object ViewEventToOutput : (HelloWorldView.Event) -> HelloWorld.Output {
    override fun invoke(event: HelloWorldView.Event): HelloWorld.Output =
        when (event) {
            HelloWorldView.Event.ButtonClicked -> Output.HelloThere
        }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial3.rib.greetings_container

import com.badoo.ribs.core.Rib
import io.reactivex.functions.Consumer

interface GreetingsContainer : Rib {

    interface Dependency {
        fun greetingsContainerOutput(): Consumer<Output>
    }

    sealed class Output {
        data class GreetingsSaid(val greeting: String) : Output()
    }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial3.rib.hello_world

import com.badoo.ribs.core.Rib

interface HelloWorld : Rib {

    interface Dependency

    class Customisation(
        val viewFactory: HelloWorldView.Factory = HelloWorldViewImpl.Factory()
    )
}
<file_sep>package com.badoo.ribs.samples.plugins.plugins_demo

import com.badoo.ribs.core.Rib

interface PluginsDemo : Rib {

    interface Dependency

}
<file_sep>package com.badoo.ribs.samples.simplerouting

import android.os.Bundle
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.badoo.ribs.test.RibsRule
import com.badoo.ribs.core.modality.BuildContext.Companion.root
import com.badoo.ribs.samples.simplerouting.rib.R
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.SimpleRoutingChild1Child1
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.builder.SimpleRoutingChild1Child1Builder
import org.junit.Rule
import org.junit.Test

class SimpleRoutingChild1Child1Test {

    @get:Rule
    val ribsRule = RibsRule { _, savedInstanceState -> buildRib(savedInstanceState) }

    private fun buildRib(savedInstanceState: Bundle?) =
        SimpleRoutingChild1Child1Builder(
            object : SimpleRoutingChild1Child1.Dependency {}
        ).build(root(savedInstanceState))

    @Test
    fun showsTitleText() {
        onView(withId(R.id.child1_child1_title))
            .check(matches(withText(R.string.child1_child1_text)))
    }
}
<file_sep>package com.badoo.ribs.samples.transitionanimations.rib.parent

import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.routing.source.backstack.operation.push
import com.badoo.ribs.samples.transitionanimations.rib.parent.routing.ParentRouter.Configuration

interface ParentPresenter {
    fun goToNext()
}

internal class ParentPresenterImpl(
    private val backStack: BackStack<Configuration>
) : ParentPresenter {

    override fun goToNext() {
        backStack.push(backStack.activeConfiguration.toNextConfiguration())
    }

    private fun Configuration.toNextConfiguration() =
        when (this) {
            is Configuration.Child1 -> Configuration.Child2
            is Configuration.Child2 -> Configuration.Child3
            is Configuration.Child3 -> Configuration.Child1
        }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.hello_world

import com.badoo.ribs.core.Rib
import com.badoo.ribs.android.text.Text
import com.badoo.ribs.tutorials.tutorial5.util.User
import io.reactivex.ObservableSource
import io.reactivex.functions.Consumer

interface HelloWorld : Rib {

    interface Dependency {
        fun helloWorldInput(): ObservableSource<Input>
        fun helloWorldOutput(): Consumer<Output>
        fun user(): User
        fun config(): Config
    }

    sealed class Input {
        data class UpdateButtonText(val text: Text): Input()
    }

    sealed class Output {
        data class HelloThere(val greeting: Text) : Output()
    }

    data class Config(
        val welcomeMessage: Text,
        val buttonText: Text
    )

    class Customisation(
        val viewFactory: HelloWorldView.Factory = HelloWorldViewImpl.Factory()
    )
}
<file_sep>package com.badoo.ribs.core.helper

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
object AnyConfiguration : Parcelable { override fun toString(): String = "AnyConfiguration" }
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.option_selector.builder

import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelector
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelectorView
import dagger.BindsInstance

@OptionSelectorScope
@dagger.Component(
    modules = [OptionSelectorModule::class],
    dependencies = [OptionSelector.Dependency::class]
)
internal interface OptionSelectorComponent {

    @dagger.Component.Factory
    interface Factory {
        fun create(
            dependency: OptionSelector.Dependency,
            @BindsInstance buildParams: BuildParams<Nothing?>,
            @BindsInstance customisation: OptionSelector.Customisation
        ): OptionSelectorComponent
    }

    fun node(): Node<OptionSelectorView>
}
<file_sep>package com.badoo.ribs.routing.state.feature

import com.badoo.ribs.routing.state.feature.RoutingStatePool.Effect.Transition.TransitionFinished
import com.badoo.ribs.routing.state.feature.RoutingStatePool.Effect.Transition.TransitionStarted
import com.badoo.ribs.routing.transition.TransitionElement
import com.badoo.ribs.test.TestConfiguration
import com.nhaarman.mockitokotlin2.*
import org.junit.Test

class OngoingTransitionTest {

    private val effectEmitter: EffectEmitter<TestConfiguration> = mock()
    private val transitionElement: TransitionElement<TestConfiguration> = mock()

    private val ongoingTransition = OngoingTransition(
        descriptor = mock(),
        actions = emptyList(),
        direction = mock(),
        transitionPair = mock(),
        transitionElements = listOf(transitionElement),
        emitter = effectEmitter,
        handler = mock()
    )

    @Test
    fun `GIVEN that there are pending transition elements WHEN transition is started THEN TransitionStarted is emitted but not TransitionFinished`() {
        whenever(transitionElement.isPending()).thenReturn(true)

        ongoingTransition.start()

        verify(effectEmitter).invoke(any<TransitionStarted<TestConfiguration>>())
        verify(effectEmitter, never()).invoke(any<TransitionFinished<TestConfiguration>>())

    }

    @Test
    fun `GIVEN that there are NOT pending transition elements WHEN transition has started THEN TransitionFinished is emitted`() {
        whenever(transitionElement.isPending()).thenReturn(false)

        ongoingTransition.start()

        verify(effectEmitter).invoke(any<TransitionFinished<TestConfiguration>>())
    }

    @Test
    fun `WHEN transition is jumpToEnd THEN TransitionFinished is emitted`() {
        ongoingTransition.jumpToEnd()

        verify(effectEmitter).invoke(any<TransitionFinished<TestConfiguration>>())
    }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.option_selector

import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.customisation.RibCustomisation
import com.badoo.ribs.android.text.Text
import io.reactivex.functions.Consumer

interface OptionSelector : Rib {

    interface Dependency {
        fun moreOptionsOutput(): Consumer<Output>
        fun moreOptionsConfig(): Config
    }

    data class Config(
        val options: List<Text>
    )

    sealed class Output

    class Customisation(
        val viewFactory: OptionSelectorView.Factory = OptionSelectorViewImpl.Factory()
    ) : RibCustomisation
}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child2.builder

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child2.SimpleRoutingChild1Child2
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child2.SimpleRoutingChild1Child2Node

class SimpleRoutingChild1Child2Builder(
    private val dependency: SimpleRoutingChild1Child2.Dependency
) : SimpleBuilder<SimpleRoutingChild1Child2>() {

    override fun build(buildParams: BuildParams<Nothing?>): SimpleRoutingChild1Child2 {
        val customisation = buildParams.getOrDefault(SimpleRoutingChild1Child2.Customisation())
        return SimpleRoutingChild1Child2Node(
            buildParams = buildParams,
            viewFactory = customisation.viewFactory(null)
        )
    }
}
<file_sep>package com.badoo.ribs.samples.dialogs.dialogs

import com.badoo.ribs.android.dialog.Dialog

class LazyDialog : Dialog<Dialog.Event>({})
<file_sep>package com.badoo.ribs.samples.comms_nodes_1.rib.container

import com.badoo.ribs.core.Rib

interface Container : Rib {

    interface Dependency
}
<file_sep>package com.badoo.ribs.tutorials.tutorial3.rib.hello_world

import androidx.lifecycle.Lifecycle
import com.badoo.ribs.clienthelper.interactor.Interactor
import com.badoo.ribs.core.modality.BuildParams

class HelloWorldInteractor(
    buildParams: BuildParams<Nothing?>
) : Interactor<HelloWorld, HelloWorldView>(
    buildParams = buildParams
) {

    override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
        super.onViewCreated(view, viewLifecycle)
    }
}
<file_sep>package com.badoo.ribs.example.auth

import android.util.Log
import com.badoo.ribs.example.BuildConfig
import com.badoo.ribs.example.network.UnsplashApi
import com.badoo.ribs.example.network.model.AccessToken
import com.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.ObservableSource
import io.reactivex.Single

interface AuthDataSource {
    val authUpdates: ObservableSource<AuthState>
    fun login(authCode: String): Single<AccessToken>
    fun loginAnonymous()
    fun logout()
    fun getState(): AuthState
}

class AuthDataSourceImpl(
    private val api: UnsplashApi,
    private val storage: AuthStateStorage,
    private val accessKey: String = BuildConfig.ACCESS_KEY,
    private val clientSecret: String = BuildConfig.CLIENT_SECRET
) : AuthDataSource {
    private val stateRelay = BehaviorRelay.createDefault<AuthState>(storage.restore())

    override val authUpdates: ObservableSource<AuthState> = stateRelay

    override fun login(authCode: String) =
        api.requestAccessToken(
            clientId = accessKey,
            clientSecret = clientSecret,
            redirectUri = AuthConfig.redirectUri,
            code = authCode
        )
            .doOnSuccess(::onAuthSuccess)

    private fun onAuthSuccess(token: AccessToken) {
        val authState = AuthState.Authenticated(token.accessToken)
        updateState(authState)
    }

    override fun loginAnonymous() {
        val state = AuthState.Anonymous
        updateState(state)
    }

    override fun logout() {
        val state = AuthState.Unauthenticated
        updateState(state)
    }

    override fun getState(): AuthState {
        val authState = stateRelay.value
        return if (authState != null) {
            authState
        } else {
            Log.e("AuthDataSource", "Cannot retrieve auth state")
            AuthState.Unauthenticated
        }
    }


    private fun updateState(authState: AuthState) {
        storage.save(authState)
        stateRelay.accept(authState)
    }
}



<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.greetings_container

import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial5.rib.greetings_container.GreetingsContainer.Output.GreetingsSaid
import com.badoo.ribs.tutorials.tutorial5.rib.greetings_container.GreetingsContainerRouter.Configuration
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorld
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorld.Output.HelloThere
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelector
import com.badoo.ribs.tutorials.tutorial5.util.BackStackInteractor
import com.jakewharton.rxrelay2.PublishRelay
import com.jakewharton.rxrelay2.Relay
import io.reactivex.functions.Consumer

class GreetingsContainerInteractor(
    buildParams: BuildParams<Nothing?>,
    output: Consumer<GreetingsContainer.Output>
) : BackStackInteractor<GreetingsContainer, Nothing, Configuration>(
    buildParams = buildParams,
    initialConfiguration = Configuration.HelloWorld
) {
    internal val helloWorldInputSource: Relay<HelloWorld.Input> = PublishRelay.create()
    internal val helloWorldOutputConsumer: Consumer<HelloWorld.Output> = Consumer {
        when (it) {
            is HelloThere -> output.accept(GreetingsSaid(it.greeting))
        }
    }

    internal val optionsSelectorOutputConsumer: Consumer<OptionSelector.Output> = Consumer {
        // TODO
    }
}
<file_sep>package com.badoo.ribs.samples.transitionanimations.rib.child3

import com.badoo.ribs.core.Rib

interface Child3 : Rib
<file_sep>package com.badoo.ribs.samples.plugins.plugins_demo

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams

class PluginsDemoBuilder(
    private val dependency: PluginsDemo.Dependency
) : SimpleBuilder<PluginsDemo>() {

    override fun build(buildParams: BuildParams<Nothing?>): PluginsDemo {
        val node = PluginsDemoNode(
            buildParams = buildParams,
            viewFactory = PluginsDemoViewImpl.Factory().invoke(null),
            plugins = emptyList()
        )

        return node
    }

}
<file_sep>package com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.builder

import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.GreetingsContainerInteractor
import com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.GreetingsContainerRouter
import dagger.Provides

@dagger.Module
internal object GreetingsContainerModule {

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun interactor(
        buildParams: BuildParams<Nothing?>
    ): GreetingsContainerInteractor =
        GreetingsContainerInteractor(
            buildParams = buildParams
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun router(
        // pass component to child rib builders, or remove if there are none
        component: GreetingsContainerComponent,
        interactor: GreetingsContainerInteractor,
        buildParams: BuildParams<Nothing?>
    ): GreetingsContainerRouter =
        GreetingsContainerRouter(
            buildParams = buildParams,
            routingSource = interactor
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun node(
        buildParams: BuildParams<Nothing?>,
        router: GreetingsContainerRouter,
        interactor: GreetingsContainerInteractor
    ) : Node<Nothing> = Node<Nothing>(
        buildParams = buildParams,
        viewFactory = null,
        plugins = listOf(interactor, router)
    )
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.hello_world.builder

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorld
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorldView

class HelloWorldBuilder(
    private val dependency: HelloWorld.Dependency
) : SimpleBuilder<Node<HelloWorldView>>() {

    override fun build(buildParams: BuildParams<Nothing?>): Node<HelloWorldView> =
        DaggerHelloWorldComponent
            .factory()
            .create(
                dependency = dependency,
                customisation = HelloWorld.Customisation(),
                buildParams = buildParams
            )
            .node()
}
<file_sep>package com.badoo.ribs.samples.transitionanimations.rib.parent

import com.badoo.ribs.core.Rib

interface Parent : Rib {

    interface Dependency

}
<file_sep>package com.badoo.ribs.core.customisation

interface RibCustomisation
<file_sep>package com.badoo.ribs.routing.source.impl

import android.os.Parcelable
import com.badoo.ribs.minimal.reactive.Cancellable
import com.badoo.ribs.minimal.reactive.Relay
import com.badoo.ribs.routing.Routing
import com.badoo.ribs.routing.history.RoutingHistory
import com.badoo.ribs.routing.history.RoutingHistoryElement
import com.badoo.ribs.routing.source.RoutingSource
import java.util.UUID

class Pool<C : Parcelable>(
    private val allowRepeatingConfigurations: Boolean
) : RoutingSource<C> {
    private var elements: Map<Routing.Identifier, RoutingHistoryElement<C>> = emptyMap()
    private val current: RoutingHistory<C>
        get() = RoutingHistory.from(
            elements.values
        )

    private val states: Relay<RoutingHistory<C>> = Relay()

    // FIXME save/restore to bundle to support correct baseLineState
    override fun baseLineState(fromRestored: Boolean): RoutingHistory<C> =
        RoutingHistory.from(
            emptySet()
        )

    fun add(
        configuration: C,
        identifier: Routing.Identifier = Routing.Identifier(
            "Set ${System.identityHashCode(this)} #$configuration.${if (allowRepeatingConfigurations) UUID.randomUUID() else "#"}"
        )
    ): Routing.Identifier {
        if (!allowRepeatingConfigurations) {
            elements
                .filter { it.value.routing.configuration == configuration }
                .map { it.key }
                .firstOrNull()?.let {
                    return it
                }
        }

        elements = elements.plus(
            identifier to RoutingHistoryElement(
                activation = RoutingHistoryElement.Activation.INACTIVE,
                routing = Routing(
                    configuration = configuration,
                    identifier = identifier
                ),
                // TODO consider overlay support -- not needed now, can be added later
                overlays = emptyList()
            )
        )

        updateState()

        return identifier
    }

    fun activate(identifier: Routing.Identifier) {
        updateActivation(identifier,
            RoutingHistoryElement.Activation.ACTIVE
        )
    }

    fun deactivate(identifier: Routing.Identifier) {
        updateActivation(identifier,
            RoutingHistoryElement.Activation.INACTIVE
        )
    }

    private fun updateActivation(identifier: Routing.Identifier, activation: RoutingHistoryElement.Activation) {
        elements[identifier]?.let {
            elements = elements
                .minus(it.routing.identifier)
                .plus(it.routing.identifier to it.copy(
                    activation = activation
                )
            )
            updateState()
        }
    }

    override fun remove(identifier: Routing.Identifier) {
        elements[identifier]?.let {
            elements = elements
                .minus(it.routing.identifier)

            updateState()
        }
    }

    private fun updateState() {
        states.emit(current)
    }

    override fun observe(callback: (RoutingHistory<C>) -> Unit): Cancellable {
        callback(current)
        return states.observe(callback)
    }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.option_selector.builder

import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelector
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelector.Output
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelectorInteractor
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelectorView
import dagger.Provides
import io.reactivex.functions.Consumer

@dagger.Module
internal object OptionSelectorModule {

    @OptionSelectorScope
    @Provides
    @JvmStatic
    internal fun interactor(
        buildParams: BuildParams<Nothing?>,
        output: Consumer<Output>,
        config: OptionSelector.Config
    ): OptionSelectorInteractor =
        OptionSelectorInteractor(
            buildParams = buildParams,
            output = output,
            options = config.options
        )

    @OptionSelectorScope
    @Provides
    @JvmStatic
    internal fun node(
        buildParams: BuildParams<Nothing?>,
        customisation: OptionSelector.Customisation,
        interactor: OptionSelectorInteractor
    ) : Node<OptionSelectorView> = Node(
        buildParams = buildParams,
        viewFactory = customisation.viewFactory.invoke(null),
        plugins = listOf(interactor)
    )
}
<file_sep>package com.badoo.ribs.tutorials.tutorial3.rib.greetings_container

import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial3.rib.greetings_container.GreetingsContainerRouter.Configuration
import com.badoo.ribs.tutorials.tutorial3.util.BackStackInteractor

class GreetingsContainerInteractor(
    buildParams: BuildParams<Nothing?>
) : BackStackInteractor<GreetingsContainer, Nothing, Configuration>(
    buildParams = buildParams,
    initialConfiguration = Configuration.Default
) {

}
<file_sep>package com.badoo.ribs.core.customisation

import com.badoo.ribs.core.Rib
import kotlin.reflect.KClass

interface MutableRibCustomisationDirectory : RibCustomisationDirectory {

    fun <T : Rib> putSubDirectory(key: KClass<T>, value: RibCustomisationDirectory)

    fun <T : RibCustomisation> put(key: KClass<T>, value: T)
}
<file_sep># Tutorial #3

## The goal of this tutorial

To see how to provide dependencies to a RIB


## Starting out

Compiling and launching **tutorial3**, this is what we see:

![](https://i.imgur.com/Ja45nRm.png)

We will provide text to the placeholders first manually, then as a dependency.

Have a brief look at the `Text` interface found in the library, as we will use it in the example:

```kotlin
package com.badoo.ribs.android

import android.content.Context

/**
 * An abstraction over text, so that you can provide it from different sources.
 *
 * In case the default implementations are not good enough, feel free to implement your own.
 */
interface Text {

    fun resolve(context: Context): String

    class Plain(private val string: String): Text {
        override fun resolve(context: Context): String =
            string
    }

    class Resource(private val resId: Int, private vararg val formatArgs: Any) : Text {
        override fun resolve(context: Context): String =
            context.resources.getString(resId, formatArgs)
    }
}

```

It provides us with an abstraction over textual information, and two simple implementations – one for actual Strings, the other for String resources. 

This is a useful approach for cases when you want to set a text from resource, but at the place of definition you don't have access to a `Context` that Android can provide, only later. Hence the `resolve(context: Context)` method.  


## A welcoming message

Let's start with the second placeholder first!

Check the `HelloWorldView` interface. Its `ViewModel` contains a dummy integer field only:

```kotlin
data class ViewModel(
    val i: Int = 0
)
```

Let's change that so that we will give a `Text` to the view to render instead:

```kotlin
data class ViewModel(
    val welcomeText: Text
)
```

Let's scroll down to the Android view implementation. Notice how it already finds a reference to the welcome text view:
```kotlin
private val welcome: TextView = androidView.findViewById(R.id.hello_world_welcome)
```

So let's implement the `ViewModel` rendering:
```kotlin
override fun accept(vm: ViewModel) {
    welcome.text = vm.welcomeText.resolve(androidView.context)
}
```

Notice how we added `welcomeText` as a `Text`, which we could now resolve using the `Context` our view has access to.


## Feeding the view with data

Let's head to our Interactor in this RIB, `HelloWorldInteractor`, where we should put business logic.

We'll find an empty block, where we can put all view-related business logic, tied to the lifecycle of the view:

```kotlin
override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
    super.onViewCreated(view, viewLifecycle)
}
```

Just to try out what we did, we could do something like this to test out the parts we just implemented in `HelloWorldView`:

```kotlin
override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
    super.onViewCreated(view, viewLifecycle)
    view.accept(initialViewModel)
}

private val initialViewModel =
    HelloWorldView.ViewModel(
        welcomeText = Text.Plain("Does this work at all?")
    )
```

Launch the app to verify that it indeed works.

![](https://i.imgur.com/maHH0pn.png)

Alright, now instead of manually fixing this, let's try and make this a dependency! 

Let's head to `HelloWorld`, the main interface of the RIB, which doesn't have any dependencies now:

```kotlin
interface HelloWorld : Rib {

    interface Dependency    
    
    // ...
}
```

Change it so that it looks like:

```kotlin
interface HelloWorld : Rib {

    interface Dependency {
        fun config(): Config
    }

    // It's a good idea to group all "simple data" dependencies into a Config 
    // object, instead of directly adding them to Dependency interface:
    data class Config(
        val welcomeMessage: Text
    )
    
    // ...
}    
```

Now if we try to build the project, Dagger will fail us, as we do not yet actually provide this dependency:

```
[Dagger/MissingBinding] com.badoo.ribs.tutorials.tutorial3.rib.hello_world.HelloWorld.Config cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface GreetingsContainerComponent extends com.badoo.ribs.tutorials.tutorial3.rib.hello_world.HelloWorld.Dependency
```

## Provide dependency directly in the parent

In this case, our dependency is simple configuration data, so we could just satisfy it directly in the parent. 

Let's head to `GreetingsContainerModule`, the place where we add all the `@Provides` DI definitions on the container level, and add this block to the bottom:

```kotlin
@GreetingsContainerScope
@Provides
@JvmStatic
internal fun helloWorldConfig(): HelloWorld.Config =
    HelloWorld.Config(
        welcomeMessage = Text.Resource(
            R.string.hello_world_welcome_text
        )
    )
```

Now the app should build, as we provide the dependency - other than that, nothing changed, since we are not using this config yet.

Let's correct that, and make use of it in the child `Interactor`.

Add the config to the constructor, and use it to construct the initial `ViewModel`:

```kotlin
class HelloWorldInteractor(
    config: HelloWorld.Config, // add this
    router: // ... remainder omitted
) {

    override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
        super.onViewCreated(view, viewLifecycle)
        view.accept(initialViewModel)
    }

    private val initialViewModel =
        HelloWorldView.ViewModel(
            welcomeText = config.welcomeMessage // use it
        )
}

```

And change the actual construction of the Interactor in `HelloWorldModule`:

```kotlin
@HelloWorldScope
@Provides
@JvmStatic
internal fun interactor(
    config: HelloWorld.Config, // add this, Dagger will provide it automatically
    router: HelloWorldRouter
): HelloWorldInteractor =
    HelloWorldInteractor(
        config = config, // pass it to the Interactor 
        router = router
    )
```

Try it out, it should now display the text we passed in as a resource:

![](https://i.imgur.com/HTSayPD.png)


## Section summary

#### Adding new data to display in the view
1. Add new widget to xml
2. Find view by id and store reference in `ViewImpl`
3. Define new field in `ViewModel`
4. Modify `accept(vm: ViewModel)` method in `ViewImpl` to actually display data
5. In case of initial `ViewModel`: pass it from `Interactor`'s `onViewCreated` method (we'll cover dynamically changing data later)


#### Adding dependency to child from parent

1. Add it to child's `Dependency` interface
2. In parent's `Module` class, add new `@Provides` annotated block
3. Use dependency in child where it's needed
    1. e.g. add it to constructor of `Interactor`
    2. go to child's `Module` / provides function
    3. add it as a parameter to `@Provides` function - this will be provided by Dagger
    4. use it in constructing the object, e.g. the `Interactor`


## Next task: greet the user

We are still left with a placeholder text to be filled with text.

Let's imagine this application has some kind of `User` object representing the current logged in user, and we want this placeholder to greet the user like: **"<NAME>!"**, with their actual name.

(There's a `User` interface included in this module, check it out)

Obviously, the `HelloWorld` RIB cannot fill in the name of the user, and will need an instance of `User` as a dependency.
 
 In the case of the welcome text, we could provide the dependency directly. Problem is, finding an instance of `User` is not the responsibility of `HelloWorld`, but it's also not the responsibility of the parent RIB, the `GreetingsContainer`.

So in this case, we will need to bubble up this dependency until at some level we can grab an instance of the current `User`, and pass it down.

(As we do not have proper logged out / logged in handling yet, we wil just pass in a dummy User object from the Activity level)

## Test your knowledge!

Based on the previous sections, you should be able to:
1. Add a new field to `HelloWorldViewImpl` that holds a reference to the `TextView` for the other placeholder, found in `rib_hello_world.xml` with the id `@+id/hello_world_title`
2. Add a new field to `HelloWorldView.ViewModel`, named `titleText`, type `Text`
3. Set the text of the `TextView` by resolving the `Text` from `titleText` whenever the `ViewModel` is rendered in `HelloWorldViewImpl`
4. Set a fixed value for this field from `HelloWorldInteractor` just to test it out.

Now let's make it more dynamic. You should also be able to:
1. Add an instance of `user: User` as a constructor dependency to `HelloWorldInteractor`
2. Use `user.name()` to construct: `Text.Resource(R.string.hello_world_title, user.name())`, and pass it as the value for `titleText` when creating the `ViewModel`
3. Add the instance of `User` as a dependency for the creation of `HelloWorldInteractor` in the respective `@Provides` function in `HelloWorldModule` 
4. Add `User` to `HelloWorld.Dependency` interface to say that `HelloWorld` RIB needs this from the outside

If you feel stuck, you can refer to the previous sections for help, or have a peek at [solutions](solutions.md).

Building the project at this point, Dagger should give you:  

`Dagger/MissingBinding] com.badoo.ribs.tutorials.tutorial3.util.User cannot be provided without an @Provides-annotated method.`


## Bubbling up dependencies

This will be super easy, if you got this far. The only difference is that if we cannot provide a dependency directly, we also add it to the `Dependency` interface of parent. We can keep doing this further until on some level we can actually provide it.

Really, just a one-liner.

Try it:

```kotlin
interface GreetingsContainer : Rib {

    interface Dependency {
        // Add this, and you are done on this level - Dagger will provide
        // it further down to children automatically:
        fun user(): User 
        fun greetingsContainerOutput(): Consumer<Output>
    }

    // ... remainder omitted
}
```
At this point we reached the root level. `RootActivity` creates an anonymous object actually providing dependencies to `GreetingsContainer`, so we have a compilation error there until we actually implement the newly added `user()` method:
 
```kotlin
/** The tutorial app's single activity */
class RootActivity : RibActivity() {

    // ... remainder omitted
    
    override fun createRib(savedInstanceState: Bundle?): Node<*> =
        GreetingsContainerBuilder(
            object : GreetingsContainer.Dependency {
                // add this block:
                override fun user(): User =
                    User.DUMMY

                override fun greetingsContainerOutput(): Consumer<GreetingsContainer.Output> =
                
                // ... remainder omitted
            }
        ).build(savedInstanceState)
}
```

The application should now build, and this is what you should see when launching:

![](https://i.imgur.com/4i2lQ1o.png)

## Tutorial complete

Congratulations! You can advance to the next one.
<file_sep>package com.badoo.ribs.samples.simplerouting

import android.os.Bundle
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.badoo.ribs.test.RibsRule
import com.badoo.ribs.core.modality.BuildContext.Companion.root
import com.badoo.ribs.samples.simplerouting.rib.R
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.SimpleRoutingChild2
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.builder.SimpleRoutingChild2Builder
import org.junit.Rule
import org.junit.Test

class SimpleRoutingChild2Test {

    @get:Rule
    val ribsRule = RibsRule { _, savedInstanceState -> buildRib(savedInstanceState) }

    private fun buildRib(savedInstanceState: Bundle?) =
        SimpleRoutingChild2Builder(
            object : SimpleRoutingChild2.Dependency {}
        ).build(root(savedInstanceState))

    @Test
    fun showsStaticText() {
        onView(withId(R.id.content_text))
            .check(matches(withText(R.string.child2_text)))
    }
}
<file_sep>package com.badoo.ribs.clienthelper.connector

import com.badoo.ribs.minimal.reactive.Relay

class NodeConnector<Input, Output>(
    override val input: Relay<Input> = Relay(),
    override val output: Relay<Output> = Relay()
): Connectable<Input, Output>
<file_sep>package com.badoo.ribs.samples.transitionanimations.rib.parent.routing

import com.badoo.ribs.samples.transitionanimations.rib.child1.Child1Builder
import com.badoo.ribs.samples.transitionanimations.rib.child2.Child2Builder
import com.badoo.ribs.samples.transitionanimations.rib.child3.Child3Builder

internal open class ParentChildBuilders {

    val child1Builder: Child1Builder = Child1Builder()
    val child2Builder: Child2Builder = Child2Builder()
    val child3Builder: Child3Builder = Child3Builder()

}
<file_sep>package com.badoo.ribs.tutorials.tutorial1.rib.hello_world

import androidx.lifecycle.Lifecycle
import com.badoo.mvicore.android.lifecycle.startStop
import com.badoo.mvicore.binder.using
import com.badoo.ribs.clienthelper.interactor.Interactor
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial1.rib.hello_world.mapper.ViewEventToOutput
import io.reactivex.functions.Consumer

class HelloWorldInteractor(
    buildParams: BuildParams<Nothing?>,
    private val output: Consumer<HelloWorld.Output>
) : Interactor<HelloWorld, HelloWorldView>(
    buildParams = buildParams
) {

    override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
        viewLifecycle.startStop {
            bind(view to output using ViewEventToOutput)
        }
    }
}
<file_sep>package com.badoo.ribs.samples.transitionanimations.rib.child2

import com.badoo.ribs.core.Rib

interface Child2 : Rib
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1

import androidx.lifecycle.Lifecycle
import com.badoo.ribs.core.plugin.ViewAware

internal class SimpleRoutingChild1Presenter(
    private val title: String
) : ViewAware<SimpleRoutingChild1View> {

    override fun onViewCreated(view: SimpleRoutingChild1View, viewLifecycle: Lifecycle) {
        view.setTitle(title)
    }
}
<file_sep>package com.badoo.ribs.samples.buildtime.rib.build_time_deps

import com.badoo.ribs.core.Rib

interface BuildTimeDeps : Rib
<file_sep>package com.badoo.ribs.samples.back_stack.rib.parent

import com.badoo.ribs.core.Rib

interface Parent : Rib {

    interface Dependency

}
<file_sep>package com.badoo.ribs.samples.comms_nodes_1.rib.container.routing

import com.badoo.ribs.samples.comms_nodes_1.rib.child1.Child1
import com.badoo.ribs.samples.comms_nodes_1.rib.child1.Child1Builder
import com.badoo.ribs.samples.comms_nodes_1.rib.child2.Child2
import com.badoo.ribs.samples.comms_nodes_1.rib.child2.Child2Builder
import com.badoo.ribs.samples.comms_nodes_1.rib.child3.Child3
import com.badoo.ribs.samples.comms_nodes_1.rib.child3.Child3Builder
import com.badoo.ribs.samples.comms_nodes_1.rib.container.Container
import com.badoo.ribs.samples.comms_nodes_1.rib.menu.Menu
import com.badoo.ribs.samples.comms_nodes_1.rib.menu.MenuBuilder

internal class ContainerChildBuilders(
    dependency: Container.Dependency
) {
    private val subtreeDeps = SubtreeDependency(dependency)

    val menu: MenuBuilder = MenuBuilder(subtreeDeps)
    val child1: Child1Builder = Child1Builder(subtreeDeps)
    val child2: Child2Builder = Child2Builder(subtreeDeps)
    val child3: Child3Builder = Child3Builder(subtreeDeps)

    class SubtreeDependency(
        dependency: Container.Dependency
    ) : Container.Dependency by dependency,
        Menu.Dependency,
        Child1.Dependency,
        Child2.Dependency,
        Child3.Dependency
}
<file_sep>package com.badoo.ribs.rx2.clienthelper.connector

import com.jakewharton.rxrelay2.PublishRelay
import com.jakewharton.rxrelay2.Relay

class NodeConnector<Input, Output>(
    override val input: Relay<Input> = PublishRelay.create(),
    override val output: Relay<Output> = PublishRelay.create()
): Connectable<Input, Output>
<file_sep>package com.badoo.ribs.tutorials.tutorial2.rib.hello_world.builder

import javax.inject.Scope

@Scope
@Retention(AnnotationRetention.RUNTIME)
internal annotation class HelloWorldScope
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1

import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.customisation.RibCustomisation

interface SimpleRoutingChild1 : Rib {

    interface Dependency {
        val title: String
    }

    class Customisation(
        val viewFactory: SimpleRoutingChild1View.Factory = SimpleRoutingChild1ViewImpl.Factory()
    ) : RibCustomisation
}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.routing

import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.SimpleRoutingChild1
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.SimpleRoutingChild1Child1
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.builder.SimpleRoutingChild1Child1Builder
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child2.SimpleRoutingChild1Child2
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child2.builder.SimpleRoutingChild1Child2Builder

internal open class SimpleRoutingChild1ChildBuilders(
    dependency: SimpleRoutingChild1.Dependency
) {
    private val subtreeDeps = SubtreeDependency(dependency)

    val child1: SimpleRoutingChild1Child1Builder = SimpleRoutingChild1Child1Builder(subtreeDeps)
    val child2: SimpleRoutingChild1Child2Builder = SimpleRoutingChild1Child2Builder(subtreeDeps)

    class SubtreeDependency(
        dependency: SimpleRoutingChild1.Dependency
    ) : SimpleRoutingChild1.Dependency by dependency,
        SimpleRoutingChild1Child1.Dependency, SimpleRoutingChild1Child2.Dependency
}
<file_sep>package com.badoo.ribs.tutorials.tutorial4.rib.greetings_container

import android.os.Parcelable
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.routing.Routing
import com.badoo.ribs.routing.resolution.ChildResolution.Companion.child
import com.badoo.ribs.routing.resolution.Resolution
import com.badoo.ribs.routing.router.Router
import com.badoo.ribs.routing.source.RoutingSource
import com.badoo.ribs.tutorials.tutorial4.rib.greetings_container.GreetingsContainerRouter.Configuration
import com.badoo.ribs.tutorials.tutorial4.rib.hello_world.builder.HelloWorldBuilder
import kotlinx.android.parcel.Parcelize

class GreetingsContainerRouter(
    buildParams: BuildParams<Nothing?>,
    routingSource: RoutingSource<Configuration>,
    private val helloWorldBuilder: HelloWorldBuilder
): Router<Configuration>(
    buildParams = buildParams,
    routingSource = routingSource
) {
    sealed class Configuration : Parcelable {
        @Parcelize object HelloWorld : Configuration()
    }

    override fun resolve(routing: Routing<Configuration>): Resolution =
        when (routing.configuration) {
            is Configuration.HelloWorld -> child { helloWorldBuilder.build(it) }
        }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial2.util

import android.os.Parcelable
import com.badoo.ribs.clienthelper.interactor.Interactor
import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.core.view.RibView
import com.badoo.ribs.routing.source.RoutingSource
import com.badoo.ribs.routing.source.backstack.BackStack

/**
 * Helper class for easier migration.
 *
 * Suggested to rather extend Interactor, create and inject back stack / other RoutingSource manually.
 */
abstract class BackStackInteractor<R : Rib, V : RibView, C : Parcelable>(
    buildParams: BuildParams<*>,
    val backStack: BackStack<C>
) : Interactor<R, V>(
    buildParams = buildParams
), RoutingSource<C> by backStack {

    constructor(
        buildParams: BuildParams<*>,
        initialConfiguration: C
    ) : this(
        buildParams = buildParams,
        backStack = BackStack(
            initialConfiguration = initialConfiguration,
            buildParams = buildParams
        )
    )
}
<file_sep>package com.badoo.ribs.tutorials.tutorial4.rib.hello_world

import com.badoo.ribs.android.text.Text
import com.badoo.ribs.core.Rib
import com.badoo.ribs.tutorials.tutorial4.util.User
import io.reactivex.functions.Consumer

interface HelloWorld : Rib {

    interface Dependency {
        fun user(): User
        fun config(): Config
        fun helloWorldOutput(): Consumer<Output>
    }

    sealed class Output {
        object HelloThere : Output()
    }

    data class Config(
        val welcomeMessage: Text
    )

    class Customisation(
        val viewFactory: HelloWorldView.Factory = HelloWorldViewImpl.Factory()
    )
}
<file_sep>package com.badoo.ribs.tutorials.tutorial1.rib.hello_world

import com.badoo.ribs.core.Rib
import io.reactivex.functions.Consumer

interface HelloWorld : Rib {

    interface Dependency {
        fun helloWorldOutput(): Consumer<Output>
    }

    sealed class Output {
        object HelloThere : Output()
    }

    class Customisation(
        val viewFactory: HelloWorldView.Factory = HelloWorldViewImpl.Factory()
    )
}
<file_sep># Routing

## Suggested read

⚠️ Before you proceed, make sure you've read:
- [Happy little trees](happy-little-trees.md)

## What's routing?

[screenshot – card swiping game]()

In this example, we have a bottom menu on this screen with three options: ```My profile```, ```Card swiping game```, ```Messages```. Pressing any of them would show the corresponding screen in the content area. Since we're building our application by building a tree of single-responsibility ```Nodes```, we can represent this with the following structure:

[diagram – container]()

At any given moment, only one of them can be active though:

[diagram – container / anim]()

We can go deeper too! The ```Messages``` screen displays either a ```Conversation list```, or – after selecting one of them – a single ```Chat```:

[screenshot – connections / list]()

[screenshot – connections / chat]()

We can represent this with the following ```Node``` structure:

[diagram – messages / container]()

Using containers in both the above examples allow us to follow a true single responsibility approach: while implementing actual "screens" is delegated to other ```Nodes```, containers keep a single one: deciding when to switch between them.

This switching mechanism is what we refer to as routing.


## Combining individual routings

The two examples above don't exist in their own vacuum though: they are directly connected. We can only see any of the ```Conversation list``` / ```Chat``` when one level higher, the main screen container switches its routing to show: ```Messages```

[diagram - combined]()


In fact, this pattern can be repeated in both directions indefinitely: we can always break up any level to more standalone pieces, and we can put any of the levels as a child under another one serving as a parent:

[diagram - app]()

Routing a pattern really starts to shine when we start to describe the whole application state with it.


## Routing: a piece of navigation as a local concern

Routing is an alternative to a global navigation pattern: instead, navigation is broken up to "local pieces", without wanting to know too much about the rest of the application.

This, among other things, also solves an age old problem of navigation in a shared module world, where shared modules shouldn't have to know about the hosting application. If navigation is a local concern, then only the direct next "piece" of it needs to present itself as a dependency!


## Navigation represented as state 

Routing describes the state of local navigation in your tree of Nodes.

The combination of these local, stateful navigation concerns uniquely describes the state of your whole application. 

In RIBs, routing is both stateful and persistent, automatically handled by the framework.

<file_sep>package com.badoo.ribs.sandbox.rib.compose_parent.routing

import android.os.Parcelable
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.routing.Routing
import com.badoo.ribs.routing.resolution.ChildResolution.Companion.child
import com.badoo.ribs.routing.resolution.Resolution
import com.badoo.ribs.routing.router.Router
import com.badoo.ribs.routing.source.RoutingSource
import com.badoo.ribs.routing.transition.handler.TransitionHandler
import com.badoo.ribs.sandbox.rib.compose_parent.routing.ComposeParentRouter.Configuration
import com.badoo.ribs.sandbox.rib.compose_parent.routing.ComposeParentRouter.Configuration.Permanent.ComposeLeaf
import kotlinx.android.parcel.Parcelize

class ComposeParentRouter internal constructor(
    buildParams: BuildParams<*>,
    private val builders: ComposeParentChildBuilders,
    transitionHandler: TransitionHandler<Configuration>? = null
): Router<Configuration>(
    buildParams = buildParams,
    routingSource = RoutingSource.permanent(ComposeLeaf),
    transitionHandler = transitionHandler
) {
    sealed class Configuration : Parcelable {
        sealed class Permanent : Configuration() {
            @Parcelize object ComposeLeaf : Permanent()
        }
    }

    override fun resolve(routing: Routing<Configuration>): Resolution =
        with(builders) {
            when (routing.configuration) {
                is ComposeLeaf -> child { composeLeaf.build(it) }
            }
        }
}

<file_sep>package com.badoo.ribs.samples.dialogs.rib.dialogs_example

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.samples.dialogs.dialogs.Dialogs
import com.badoo.ribs.samples.dialogs.rib.dialogs_example.DialogsRouter.Configuration.Content

class DialogsBuilder(
    private val deps: DialogsExample.Dependency
) : SimpleBuilder<DialogsExample>() {

    private val dialogs = Dialogs()

    override fun build(buildParams: BuildParams<Nothing?>): DialogsExample {
        val backStack: BackStack<DialogsRouter.Configuration> = BackStack(
            initialConfiguration = Content.Default,
            buildParams = buildParams
        )
        val presenter = DialogsPresenterImpl(dialogs, backStack)
        val router = DialogsRouter(
            buildParams = buildParams,
            routingSource = backStack,
            dialogLauncher = deps.dialogLauncher,
            dialogs = dialogs
        )
        val viewDependencies = object : DialogsView.Dependency {
            override val presenter: DialogsPresenter = presenter
        }
        return DialogsNode(
            buildParams = buildParams,
            viewFactory = DialogsViewImpl.Factory().invoke(viewDependencies),
            plugins = listOf(router, presenter)
        )
    }
}
<file_sep>version = VERSION_NAME
group = GROUP

def getReleaseRepositoryUrl() {
    return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
            : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
}

def getSnapshotRepositoryUrl() {
    return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
            : "https://oss.sonatype.org/content/repositories/snapshots/"
}

apply plugin: "com.vanniktech.maven.publish"

mavenPublish {
    targets {
        uploadArchives {
            releaseRepositoryUrl = getReleaseRepositoryUrl()
            snapshotRepositoryUrl = getSnapshotRepositoryUrl()
        }
    }
}

project.afterEvaluate {
    project.tasks.named("androidSourcesJar").configure {
        from project.android.sourceSets.main.java.srcDirs
    }
    project.tasks.register("install").configure { task ->
        task.dependsOn("installArchives")
    }
}
<file_sep>package com.badoo.ribs.samples.back_stack.rib.parent

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.samples.back_stack.rib.parent.routing.ParentChildBuilders
import com.badoo.ribs.samples.back_stack.rib.parent.routing.ParentRouter
import com.badoo.ribs.samples.back_stack.rib.parent.routing.ParentRouter.Configuration
import com.badoo.ribs.samples.back_stack.rib.parent.routing.ParentRouter.Configuration.Content

class ParentBuilder(
    private val dependency: Parent.Dependency
) : SimpleBuilder<Parent>() {

    private val builders by lazy { ParentChildBuilders(dependency) }

    override fun build(buildParams: BuildParams<Nothing?>): Parent {
        val backStack  = backStack(buildParams)
        val router = router(buildParams, backStack)
        val presenter = presenter(backStack)

        val viewDependency = object : ParentView.Dependency {
            override val presenter: ParentPresenter = presenter
        }

        return ParentNode(
            buildParams = buildParams,
            viewFactory = ParentViewImpl.Factory().invoke(viewDependency),
            plugins = listOfNotNull(
                router,
                presenter
            )
        )
    }

    private fun backStack(buildParams: BuildParams<Nothing?>): BackStack<Configuration> =
        BackStack(
            buildParams = buildParams,
            initialConfiguration = Content.A
        )

    private fun router(
        buildParams: BuildParams<Nothing?>,
        backStack: BackStack<Configuration>
    ): ParentRouter =
        ParentRouter(
            buildParams = buildParams,
            routingSource = backStack,
            builders = builders
        )

    private fun presenter(backStack: BackStack<Configuration>): ParentPresenterImpl =
        ParentPresenterImpl(backStack)

}
<file_sep>package com.badoo.ribs.test

import android.os.Parcelable

sealed class TestConfiguration : Parcelable
<file_sep>package com.badoo.ribs.tutorials.tutorial4.rib.greetings_container

import com.badoo.ribs.core.Rib
import com.badoo.ribs.tutorials.tutorial4.util.User
import io.reactivex.functions.Consumer

interface GreetingsContainer : Rib {

    interface Dependency {
        fun user(): User
        fun greetingsContainerOutput(): Consumer<Output>
    }

    sealed class Output {
        data class GreetingsSaid(val greeting: String) : Output()
    }
}
<file_sep>package com.badoo.ribs.samples.dialogs.rib.dialogs_example

import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.core.plugin.Plugin
import com.badoo.ribs.core.view.ViewFactory

internal class DialogsNode(
    buildParams: BuildParams<*>,
    viewFactory: ViewFactory<DialogsView>,
    plugins: List<Plugin>
) : Node<DialogsView>(
    buildParams = buildParams,
    viewFactory = viewFactory,
    plugins = plugins
), DialogsExample
<file_sep>package com.badoo.ribs.test


import android.os.Bundle
import androidx.test.rule.ActivityTestRule
import com.badoo.ribs.RibTestActivity
import com.badoo.ribs.core.Rib
import org.junit.runner.Description
import org.junit.runners.model.Statement

open class RibsRule(
    builder: ((RibTestActivity, Bundle?) -> Rib)? = null
): ActivityTestRule<RibTestActivity>(
    RibTestActivity::class.java, true, builder != null
) {
    init {
        RibTestActivity.ribFactory = builder
    }

    fun start(ribFactory: ((RibTestActivity, Bundle?) -> Rib)) {
        RibTestActivity.ribFactory = ribFactory
        launchActivity(null)
    }

    override fun apply(base: Statement, description: Description?): Statement =
        super.apply(object : Statement() {
            override fun evaluate() {
                try {
                    base.evaluate()
                } finally {
                    RibTestActivity.ribFactory = null
                }
            }
        }, description)
}
<file_sep>package com.badoo.ribs.samples.transitionanimations.rib.parent

import android.view.ViewGroup
import android.widget.Button
import androidx.annotation.LayoutRes
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.customisation.inflate
import com.badoo.ribs.core.view.AndroidRibView
import com.badoo.ribs.core.view.RibView
import com.badoo.ribs.core.view.ViewFactory
import com.badoo.ribs.core.view.ViewFactoryBuilder
import com.badoo.ribs.samples.transitionanimations.R

interface ParentView : RibView {

    interface Factory : ViewFactoryBuilder<Dependency, ParentView>

    interface Dependency {
        val presenter: ParentPresenter
    }
}

class ParentViewImpl private constructor(
    override val androidView: ViewGroup,
    private val presenter: ParentPresenter
) : AndroidRibView(),
    ParentView {

    class Factory(
        @LayoutRes private val layoutRes: Int = R.layout.rib_parent
    ) : ParentView.Factory {
        override fun invoke(deps: ParentView.Dependency): ViewFactory<ParentView> =
            ViewFactory {
                ParentViewImpl(
                    androidView = it.inflate(layoutRes),
                    presenter = deps.presenter
                )
            }
    }

    private val nextButton: Button = androidView.findViewById(R.id.next_button)
    private val container: ViewGroup = androidView.findViewById(R.id.container)

    init {
        nextButton.setOnClickListener {
            presenter.goToNext()
        }
    }

    override fun getParentViewForSubtree(subtreeOf: Node<*>): ViewGroup = container
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.greetings_container.builder

import com.badoo.ribs.android.text.Text
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial5.R
import com.badoo.ribs.tutorials.tutorial5.rib.greetings_container.GreetingsContainer
import com.badoo.ribs.tutorials.tutorial5.rib.greetings_container.GreetingsContainerInteractor
import com.badoo.ribs.tutorials.tutorial5.rib.greetings_container.GreetingsContainerRouter
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.HelloWorld
import com.badoo.ribs.tutorials.tutorial5.rib.hello_world.builder.HelloWorldBuilder
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelector
import dagger.Provides
import io.reactivex.ObservableSource
import io.reactivex.functions.Consumer

@dagger.Module
internal object GreetingsContainerModule {

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun router(
        // pass component to child rib builders, or remove if there are none
        component: GreetingsContainerComponent,
        buildParams: BuildParams<Nothing?>,
        interactor: GreetingsContainerInteractor
    ): GreetingsContainerRouter =
        GreetingsContainerRouter(
            buildParams = buildParams,
            helloWorldBuilder = HelloWorldBuilder(component),
            routingSource = interactor
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun interactor(
        buildParams: BuildParams<Nothing?>,
        output: Consumer<GreetingsContainer.Output>
    ): GreetingsContainerInteractor =
        GreetingsContainerInteractor(
            buildParams = buildParams,
            output = output
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun lexemes(): List<@JvmSuppressWildcards Text> =
        listOf(
            Text.Plain("Hello"),
            Text.Plain("Grüss gott"),
            Text.Plain("Bonjour"),
            Text.Plain("Hola"),
            Text.Plain("Szép jó napot"),
            Text.Plain("Góðan dag")
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun node(
        buildParams: BuildParams<Nothing?>,
        router: GreetingsContainerRouter,
        interactor: GreetingsContainerInteractor
    ) : Node<Nothing> = Node<Nothing>(
        buildParams = buildParams,
        viewFactory = null,
        plugins = listOf(interactor, router)
    )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun helloWorldConfig(
        options: @JvmSuppressWildcards List<Text>
    ): HelloWorld.Config =
        HelloWorld.Config(
            welcomeMessage = Text.Resource(R.string.hello_world_welcome_text),
            buttonText = options.first()
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun helloWorldOutputConsumer(
        interactor: GreetingsContainerInteractor
    ) : Consumer<HelloWorld.Output> =
        interactor.helloWorldOutputConsumer

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun helloWorldInputSource(
        interactor: GreetingsContainerInteractor
    ) : ObservableSource<HelloWorld.Input> =
        interactor.helloWorldInputSource

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun moreOptionsConfig(
        options: @JvmSuppressWildcards List<Text>
    ): OptionSelector.Config =
        OptionSelector.Config(
            options = options
        )

    @GreetingsContainerScope
    @Provides
    @JvmStatic
    internal fun moreOptionsOutput(
        interactor: GreetingsContainerInteractor
    ): Consumer<OptionSelector.Output> =
        interactor.optionsSelectorOutputConsumer
}
<file_sep># Tutorial #1

## Before you start

1. Clone the repo:
   ```
   git clone https://github.com/badoo/RIBs
   ```

2. Open the project in Android Studio

## The goal of this tutorial
Building and attaching an existing RIB to an integration point. Hello world!


## How an app using RIBs is structured
1. There's one or more integration points in the form of `Activities` / `Fragments` / etc. Ideally the app should be single-Activity, but the library makes no difference about it.
2. Integration points build a `Node` (can be any `Node`), and attach it to themselves, propagating all lifecycle methods to it. This `Node` will be the root. 
3. A tree structure of `Node`s represent the application's business logic state.
4. A tree structure of `View`s represent the application on the view level. This tree is not necessarily the same as the `Node` tree, as `Node`s do not necessarily have their own views, or might be attached to a different place in the view tree (dialogs). We'll see examples about these later.


## How a RIB is structured
Open up the `tutorial1` module, which has a single RIB in the `com.badoo.ribs.tutorials.tutorial1.rib.hello_world` package.

It contains a bunch of classes and packages, where to start?

The immediate go-to when checking out any RIB you are not familiar with, is the **main interface** of the RIB, which serves as the documentation of its public API.

In Badoo RIBs, this main interface always has the name of the RIB itself without any suffix, and you can find it in the root of the package.

An example of such a public interface typically looks like:
```kotlin

interface ExampleRib : Rib {

    interface Dependency : Rib.Dependency {
        fun exampleRibInput(): ObservableSource<Input>
        fun exampleRibOutput(): Consumer<Output>
        // + other dependencies
    }

    sealed class Input {
        // possible Inputs
    }
    
    sealed class Output {
        // possible Outputs
    }

    class Customisation(
        val viewFactory: ViewFactory<HelloWorldView> = inflateOnDemand(
            R.layout.rib_hello_world
        )
    )
}
```


### Dependencies
Here, the `Dependency` interface describes all external dependencies this RIB needs to be built.

RIBs act as a black box that can be plugged in anywhere. This means, that as long as you can provide their dependencies, you can build them, and they are supposed to be auto-wired and ready to go immediately without any further actions.


### Inputs and Outputs

RIBs can also have `Inputs` / `Outputs`. These represent the way we can communicate with them, and they are always defined as part of the public API.

We'll see this in practical examples, but for now, the only things that matter, is that:
- if a RIB has `Input`, then it also has a dependency of `ObservableSource<Input>`
- if a RIB has `Output`, then it also has a dependency of `Consumer<Output>`

This is important to satisfy the plug-and-play functionality of the RIB. Every RIB implementation guarantees that as long as you can give it a source of its `Inputs` that it can observe, and a consumer of its possible `Outputs` that will handle them, it will do all the required wiring internally, and will "just work" out of the box. 


## Getting our hands dirty

Enough theory, let's get down to the practical details, and check out `HelloWorld` interface:

```kotlin
package com.badoo.ribs.tutorials.tutorial1.rib.hello_world

interface HelloWorld : Rib {

    interface Dependency {
        fun helloWorldOutput(): Consumer<Output>
    }

    sealed class Output {
        object HelloThere : Output()
    }

    class Customisation(
        val viewFactory: ViewFactory<HelloWorldView> = inflateOnDemand(
            R.layout.rib_hello_world
        )
    )
}
```

The new thing here is the `Customisation` part, but we'll talk about that in other tutorials. For now, it's enough to understand that this RIB will have a default view inflated from `R.layout.rib_hello_world`.

This RIB does not have any `Inputs`, and it has only one possible `Output`: `HelloThere`. Following from what we said above, this also means that it has a dependency of someone to consume this `Output`.

It has no other dependencies, so as long as we can give it a `Consumer`, we can build it!

Let's take it for a test run!

Open up `RootActivity` in the `tutorial1` module:

```kotlin
/** The tutorial app's single activity */
class RootActivity : RibActivity() {

    /* remainder omitted */

    override fun createRib(savedInstanceState: Bundle?): Node<*> =
        TODO("Create HelloWorldBuilder, supply dependencies in constructor, return built Node")
}
``` 
This `Activity` extends the `RibActivity` class from the library, which is just a convenience class to forward all lifecycle methods to a single root RIB. It has an abstract method `createRib(): Node<*>` which we need to implement here.

To do this, we first need to create an instance of `HelloWorldBuilder`. Opening this class we can see that it indeed needs an instance of the `Dependency` interface defined in the public API of the RIB:

```kotlin
class HelloWorldBuilder(dependency: HelloWorld.Dependency) {
    
    /* remainder omitted */
    
    fun build(buildContext: BuildContext.Params): Node<HelloWorldView> {
        /* remainder omitted */
    }
}
``` 

And also that it has a `build` method which will give us the `Node` object we need to return from the `createRib()` method.

So let's do that!

```kotlin
/** The tutorial app's single activity */
class RootActivity : RibActivity() {

    /* remainder omitted */

    override fun createRib(savedInstanceState: Bundle?): Node<*> =
        HelloWorldBuilder(
            object : HelloWorld.Dependency {
                override fun helloWorldOutput(): Consumer<HelloWorld.Output> = Consumer {
                    // not sure what to do here yet
                }
            }
        ).build(BuildContext.root(savedInstanceState))
}
```

Now the project should compile. Build it, and we should see this screen:

![](https://i.imgur.com/c2fUEHO.png)

And checking the view hierarchy, this is what we should see:

[![View hierarchy of HelloWorld RIB](https://i.imgur.com/cf2gi9N.png)](https://i.imgur.com/Xkh2XmP.png)

So indeed, that button we see is coming from the RIB we built! Yay!

Now this doesn't do to much. So let's change the `Consumer` dependency that we pass to the `Builder` so that it displays a Snackbar:

```kotlin
override fun createRib(savedInstanceState: Bundle?): Node<*> =
        HelloWorldBuilder(
            object : HelloWorld.Dependency {
                override fun helloWorldOutput(): Consumer<HelloWorld.Output> = Consumer {
                    Snackbar.make(rootViewGroup, "Hello world!", Snackbar.LENGTH_SHORT).show()
                }
            }
        ).build(
            // You only ever need to supply this info manually at your root RIB
            // All other children get it from the framework
            BuildContext.Params(
                ancestryInfo = AncestryInfo.Root,
                savedInstanceState = savedInstanceState
            )
        )
```

Build and deploy the app, and pressing the button this is what we should see:

![](https://i.imgur.com/F5GEBpw.png)

As promised, beyond providing the required dependencies, we did not need to make any additional wiring - the HelloWorld RIB takes care of the setup that makes the button click invoke the lambda that we passed in.

## 🎉 🎉 🎉 Congratulations  🎉 🎉 🎉
 You just built your first RIB!
 
 You can advance to the next tutorial, or continue reading for some additional understanding of the internals.

## Further reading - internals of the HelloWorld RIB

If we want to understand *why* pressing the button on the UI actually invokes showing the Snackbar, we'll have to check some other classes in the `hello_world` package, too.

### The View

Let's start from the view side, and open up `HelloWorldView`. We see an interface and an implementation of it. Let's start with the interface:

```kotlin
interface HelloWorldView : RibView,
    ObservableSource<Event>,
    Consumer<ViewModel> {

    sealed class Event {
        object ButtonClicked : Event()
    }

    /* remainder omitted */
}
```

Views (as well as other components in RIBs) are reactive. 

This `HelloWorldView` interface states that any implementation of the view is a source of `Events` (here a single one for `ButtonClicked`) and a consumer of `ViewModels` (which we are not using now).


### The View implementation

You can check in the Android view implementation right below the interface how this is done:

```kotlin
class HelloWorldViewImpl( 
    /* remainder omitted */
) {
    /* remainder omitted */
    
    private val button: Button by lazy { findViewById<Button>(R.id.hello_world_button) }

    override fun onFinishInflate() {
        super.onFinishInflate()
        button.setOnClickListener { events.accept(Event.ButtonClicked) }
    }

    /* remainder omitted */
}
``` 

Any click on the button with the id `R.id.hello_world_button` will result in the associated `ButtonClicked` event being published.

The `View` itself never cares for how a certain UI interaction affects anything related to business logic. It only renders `ViewModels`, and triggers `Events` based on the user interaction.

So where is this event being used?


### Connecting the dots

Open up `HelloWorldInteractor`, which is responsible for connecting different parts of business logic:

```kotlin
class HelloWorldInteractor(
    /* remainder omitted */
    private val output: Consumer<HelloWorld.Output>
    /* remainder omitted */
) {

    override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
        viewLifecycle.startStop {
            bind(view to output using ViewEventToOutput)
        }
    }
}
``` 

The only important parts are that:
1. This class receives `output` in its constructor. This is the object that we required when we declared the RIB's dependencies - the consumer which knows how to react to our `Output` event -, and is passed here via dependency injection. If you are familiar with Dagger, you can check `HelloWorldComponent` and `HelloWorldModule` classes in the `builder` package to see how.

2. The maybe strange looking syntax in onViewCreated is a lifecycle-scoped binding between two reactive endpoints. Here, these two endpoints are the `view` (reactive source of `HelloWorldView.Event`) and the `output` (reactive consumer of HelloWorld.Output), and the scope of the binding is a START-STOP cycle. As the two endpoints have different types, the binding uses a simple mapper `ViewEventToOutput` found in the `mapper` package. In this implementation it provides a simple 1:1 mapping between the two types:

```kotlin
object ViewEventToOutput : (HelloWorldView.Event) -> HelloWorld.Output {
    override fun invoke(event: HelloWorldView.Event): HelloWorld.Output =
        when (event) {
            HelloWorldView.Event.ButtonClicked -> Output.HelloThere
        }
}
```

### Sidenote ###
Badoo RIBs uses [MVICore](https://github.com/badoo/MVICore) both under the hood and in RIB implementations for state machines as well as providing the tools to connect reactive components.

Both the `viewLifecycle.startStop` and the `bind(view to output using ViewEventToOutput)` parts come from the library. You can check out its [documentation](https://github.com/badoo/MVICore) for more information about these topics.


### To sum it up ###
1. The default view is inflated from `R.layout.rib_hello_world` (defined in main interface), which gives us an instance of `HelloWorldViewImpl`
2. `HelloWorldViewImpl` finds a button in the view hierarchy by id
3. An onClickListener is set on it that will trigger publishing an `Event` defined in `HelloWorldView` interface
4. Interactor will:
    1. take `HelloWorldView` as a reactive output
    2. transforms elements coming from it using a mapper
    3. connect this transformed stream of `Output` events to the output consumer (which we got from our dependencies)
    
The result is a clear separation of concerns. View implementation doesn't care what the event will trigger, and even the whole RIB doesn't care what should happen once a certain `Output` is triggered. You can integrate this same RIB in different places, and provide a `Consumer` dependency that shows a Snackbar in one place, and one that shows a Toast in another.

<file_sep>package com.badoo.ribs.tutorials.tutorial4.rib.hello_world

import androidx.lifecycle.Lifecycle
import com.badoo.mvicore.android.lifecycle.startStop
import com.badoo.mvicore.binder.using
import com.badoo.ribs.android.text.Text
import com.badoo.ribs.clienthelper.interactor.Interactor
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial4.R
import com.badoo.ribs.tutorials.tutorial4.rib.hello_world.mapper.ViewEventToOutput
import com.badoo.ribs.tutorials.tutorial4.util.User
import io.reactivex.functions.Consumer

class HelloWorldInteractor(
    user: User,
    config: HelloWorld.Config,
    private val output: Consumer<HelloWorld.Output>,
    buildParams: BuildParams<Nothing?>
) : Interactor<HelloWorld, HelloWorldView>(
    buildParams = buildParams
) {

    override fun onViewCreated(view: HelloWorldView, viewLifecycle: Lifecycle) {
        super.onViewCreated(view, viewLifecycle)
        view.accept(initialViewModel)
        viewLifecycle.startStop {
            bind(view to output using ViewEventToOutput)
        }
    }

    private val initialViewModel =
        HelloWorldView.ViewModel(
            titleText = Text.Resource(R.string.hello_world_title, user.name()),
            welcomeText = config.welcomeMessage
        )
}
<file_sep>package com.badoo.ribs.samples.retained_instance_store.rib.retained_instance_example

import com.badoo.ribs.core.Rib

interface RetainedInstanceExample : Rib<file_sep># Base concepts

## Terminology

This section contains the suggested knowledge before starting with the tutorials. Don't worry if not everything is clear, you will get the "Aha!" moment along the way.

### RIB
A self-contained black box of business logic along with its view. RIB as a word refers to this conceptual unit as a whole, not a class. On the practical level, RIBs have the following main components: `Builder`, `Node`, `Router`, `Interactor`, `View` (optional).

### Builder
A class responsible for the building operation: `(Dependencies) -> Node`.

### Node
The physical embodiment of a RIB, the actual result of the `Builder`'s build operation. A `Node` is responsible for:
- implementing the lifecycle of the RIB as a whole 
- keeping reference to its immediate child `Node`s + propagating lifecycle methods to them 
- implementing all tree operations (attach/detach child and child view) automatically

### Router
Implements high-level routing logic by:
- defining a set of possible configurations the RIB can be in
- resolving those configurations to `RoutingActions`, such as (but not limited to) attaching another `Node` as a child
- maintains an automatically persisted back stack of configurations, and offers and API to manipulate it (operations like push, pop, etc.)

### Interactor
Implements business logic:
- connects reactive pieces (e.g state machines, view, inputs and outputs of children)
- can set the RIB in any pre-defined configuration via calling on operations offered by `Router` 

### View
Reponsible for rendering a ViewModel and triggering events, nothing more. Since it's just an interface, can also be customised or mocked when testing.





<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.option_selector.builder

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelector
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelectorView

class OptionSelectorBuilder(
    private val dependency: OptionSelector.Dependency
) : SimpleBuilder<Node<OptionSelectorView>>() {

    override fun build(buildParams: BuildParams<Nothing?>): Node<OptionSelectorView> =
        DaggerOptionSelectorComponent
            .factory()
            .create(
                buildParams = buildParams,
                dependency = dependency,
                customisation = OptionSelector.Customisation()
            )
            .node()
}
<file_sep># Jetpack Compose support

RIBs support Jetpack Compose!

## Dependencies

All artifacts should be available from jitpack with a `-compose` suffix too.

See [Module dependencies](../setup/deps.md)


## Building the project locally

If you'd like to play around in this repo, add this flag in your `local.properties`, then hit gradle sync:

```
useCompose=true
```

What you get:
- Some new modules enabled (you can check `settings.gradle` to see which)
- Compose-based implementation of `RibView`: `ComposeRibView`
- Demo in `sandbox` app
<file_sep>package com.badoo.ribs.samples.transitionanimations.rib.child1

import com.badoo.ribs.core.Rib

interface Child1 : Rib
<file_sep>package com.badoo.ribs.samples.simplerouting.app

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.ribs.android.RibActivity
import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.modality.BuildContext
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_parent.SimpleRoutingParent
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_parent.builder.SimpleRoutingParentBuilder

class RootActivity : RibActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_root)
        super.onCreate(savedInstanceState)
    }

    override val rootViewGroup: ViewGroup
        get() = findViewById(R.id.root)

    override fun createRib(savedInstanceState: Bundle?): Rib =
        SimpleRoutingParentBuilder(
            object : SimpleRoutingParent.Dependency {}
        ).build(
            BuildContext.root(savedInstanceState)
        )
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.option_selector

import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.RadioButton
import android.widget.RadioGroup
import androidx.annotation.LayoutRes
import com.badoo.ribs.core.view.RibView
import com.badoo.ribs.core.view.ViewFactoryBuilder
import com.badoo.ribs.core.customisation.inflate
import com.badoo.ribs.tutorials.tutorial5.R
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelectorView.Event
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelectorView.ViewModel
import com.badoo.ribs.android.text.Text
import com.badoo.ribs.core.view.AndroidRibView
import com.badoo.ribs.core.view.ViewFactory
import com.jakewharton.rxrelay2.PublishRelay
import io.reactivex.ObservableSource
import io.reactivex.functions.Consumer


interface OptionSelectorView : RibView,
    ObservableSource<Event>,
    Consumer<ViewModel> {

    sealed class Event

    data class ViewModel(
        val options: List<Text>
    )

    interface Factory : ViewFactoryBuilder<Nothing?, OptionSelectorView>
}


class OptionSelectorViewImpl private constructor(
    override val androidView: ViewGroup,
    private val events: PublishRelay<Event> = PublishRelay.create()
) : AndroidRibView(),
    OptionSelectorView,
    ObservableSource<Event> by events,
    Consumer<ViewModel> {

    class Factory(
        @LayoutRes private val layoutRes: Int = R.layout.rib_option_selector
    ) : OptionSelectorView.Factory {
        override fun invoke(deps: Nothing?): ViewFactory<OptionSelectorView> = ViewFactory {
            OptionSelectorViewImpl(
                it.inflate(layoutRes)
            )
        }
    }

    private val radioGroup: RadioGroup = androidView.findViewById(R.id.more_options_radio_group)
    private val confirmButton: Button = androidView.findViewById(R.id.more_options_confirm_selection)

    init {
        // TODO set click listener on confirmButton that will emit ConfirmButtonClicked
        //  hint: use radioGroup.checkedIndex to set the value of ConfirmButtonClicked.selectionIndex
    }

    private val RadioGroup.checkedIndex: Int
        get() {
            val checkedRadio = findViewById<RadioButton>(checkedRadioButtonId)
            return indexOfChild(checkedRadio)
        }

    override fun accept(vm: ViewModel) {
        createRadios(vm.options)
    }

    private fun createRadios(options: List<Text>) {
        radioGroup.removeAllViews()

        options.forEachIndexed { idx, lexem ->
            RadioButton(androidView.context).apply {
                text = lexem.resolve(context)
                id = View.generateViewId()
                radioGroup.addView(this)
                isChecked = idx == 0
            }
        }
    }
}
<file_sep># Tutorial #5

## The goal of this tutorial

Understanding how to dynamically switch between child RIBs


## Starting out

![](https://i.imgur.com/TIfGFtw.png)

Launch the **tutorial5** app, and you can see that there's a new button in the layout: **MORE OPTIONS**. 

If you look at the project structure, you can also find a new RIB in this module: `OptionSelector`. 

All it does is it renders a screen with text options and a confirm button. We will use that to update the button text in our `HelloWorld` rib, and also use it for the actual greeting shown in the Snackbar.

![](https://i.imgur.com/hz7ZXlQ.png)


## Hiererachy of multi-child RIBs

We had this hiearachy so far:

```
GreetingsContainer
└── HelloWorld
```

And now we'll add `OptionsSelector` as the second child of `GreetingsContainer`:

```
GreetingsContainer
├── HelloWorld
└── OptionsSelector
```

The idea how they will work together:

1. On `HelloWorld` screen, User presses **MORE OPTIONS**. Since it is beyond the responsibility of `HelloWorld` RIB, it reports it as `Output`
2. `GreetingsContainer` catches the output, and switches its routing from `HelloWorld` to `OptionsSelector`. Since we display the container on the whole screen, this results in a "new screen" effect.
3. `OptionsSelector` offers UI interaction to select something from a radio group. What should happen when a certain options is selected is beyond its responsibilities, so similarly as with `HelloWorld`, it reports it as `Output`.
4. `GreetingsContainer` catches the output, switches back its routing to `HelloWorld` again.
5. The text of the main button in `HelloWorld` should be updated to reflect the newly selected option. This can be done by via an `Input` command to `HelloWorld` which allows setting of the text from outside of the RIB.
 

## Test your knowledge

By now you should be able to:
1. Trigger a new event from the UI that reaches the parent as `Output`
    1. Add a new element to `Output` in `HelloWorld` called `ShowMoreOptions`
    2. Add a new element to `Event` in `HelloWorldView` called `MoreOptionsButtonClicked`
    3. In `HelloWorldView`, set a click listener on `moreOptionsButton` that will publish `MoreOptionsButtonClicked`
    4. Add the transformation between `Event` and `Output` in the `viewEventToOutput` transformer
    5. React to this new output in `GreetingsContainerInteractor`. Leave the actual implementation a `TODO()`
2. Add `OptionSelector` RIB as a child of `GreetingsContainer`. This involves:
    1. making `GreetingsContainerComponent` extend child dependency interface
    1. satisfying child dependencies (prepared for you in `GreetingsContainerModule`)
    2. providing `optionsSelectorBuilder` to the `GreetingsContainerRouter`
    3. adding a new Configuration to `GreetingsContainerRouter`: "OptionsSelector"
    4. resolving it to an `child { optionsSelectorBuilder.build() }` action
 
For help with the above tasks, you can refer to:
- **tutorial1** / **Further reading** section on how to make a Button trigger an `Output`
- **tutorial2** / **Summary** section on how to add a child RIB to a parent
- **tutorial4** on commnunication with child RIBs, i.e. `Inputs` / `Outputs`


## Implement routing

Right now:
1. our new Button can signal the correct `Output`
2. the container's `Router` can build the other RIB we need

The only thing we need is to connect the dots, so that `1.` actually triggers doing `2.` 

Business logic triggers routing:
1. in `GreetingsContainerInteractor` we consume the `Output` of `HelloWorld`
2. in the `when` branch for the new `Output` (where we added a `TODO()`) we want to tell `GreetingsContainerRouter` to switch to the Configuration representing `OptionSelector` RIB.

All we need to do is:

```kotlin
class GreetingsContainerInteractor
    // ...

    internal val helloWorldOutputConsumer: Consumer<HelloWorld.Output> = Consumer {
        when (it) {
            HelloThere -> output.accept(GreetingsSaid("Someone said hello"))
            ShowMoreOptions -> router.push(Configuration.OptionsSelector)    
        }
    }
}
```

Pressing the **MORE OPTIONS** button the app should display the new screen:

![](https://i.imgur.com/s0Udusa.png)

Try it!
 
Right now the only way of getting back to `HelloWorld` is to press back on the device. We'll address that soon.


## Explanation: the back stack

Why did the above work?

All `Routers` have a routing back stack. By default, this back stack has a single element:

```
back stack = [(initial configuration)]
```

This is the one you set in your `Router`. In `GreetingsContainerRouter` this reads:

```kotlin
class GreetingsContainerRouter(
    // ...
    initialConfiguration = Configuration.HelloWorld
)
```

So our default back stack was in fact:
```
back stack = [*Configuration.HelloWorld] 
```

A configuration can be either **active**/**inactive**. In simple terms, it's **active** if it's on screen.

A simplified rule of the back stack is that only the last configuration is **active**. We'll mark this with an asterisk (*) from now on.

`Router` offers you operations to manipulate this back stack.

```kotlin
fun push(configuration: Content)

fun popBackStack(): Boolean
```

There are other operations too, but we'll discuss them later in other tutorials. What's important is that `push` adds a new element to the end of the back stack, while `popBackStack` removes the last one from the end.

So when we did ```router.push(Configuration.OptionsSelector)```, this happened:

```
back stack 0 = [*Configuration.HelloWorld] 
// push
back stack 1 = [Configuration.HelloWorld, *Configuration.OptionsSelector]
```

And because we just said that the last element in the back stack is  **active** (on screen), this means that the view of `HelloWorld` gets detached, and `OptionsSelector` gets created and attached.


## Back pressing and the back stack

The reverse is happening when we pressed back.

By default, back pressing is propagated to the deepest **active** levels of the RIB tree by default, where each RIB has a chance to react to it:

1. `Interactor` has a chance to override `handleBackPress()` to do something based on business logic
2. `Router` will be asked if it has back stack to pop. If yes, pop is triggered and nothing else is done.
3. If `Router` had only one more configuration left in its back stack, there's nothing more to remove. The whole thing starts bubbling up the RIB tree to higher levels until one of the levels can handle it (points 1 and 2). If it is handled, the propagation stops.
4. If the whole RIB tree didn't handle the back press, then the last fallback is the Android environment (in practice this probably means that the hosting `Activity` finishes).

In our case, when we were on the `OptionsSelector` screen, `GreetingsContainerRouter` had 2 elements in its back stack, so it could automatically handle the back press event by popping the latest:

```
back stack 0 = [*Configuration.HelloWorld] 
// push
back stack 1 = [Configuration.HelloWorld, *Configuration.OptionsSelector]
// back press
back stack 2 = [*Configuration.HelloWorld]
```

And again, because last element in the back stack is on screen, this means that `OptionsSelector` gets detached, and `HelloWorld` gets attached back to the screen again.


## Almost there: use the result from options screen

Of course there's no point of opening the second screen if we cannot interact with it and our only option is to press back.

So let's make it a bit more useful:
1. Add a new element to `Output` in `OptionsSelector`: ```data class OptionSelected(val text: Text) : Output()```
2. Add a new element to `Event` in `OptionsSelectorView`: ```data class ConfirmButtonClicked(val selectionIndex: Int) : Event()```
3. In `OptionsSelectorViewImpl`, add a click listener on `confirmButton` that will trigger this event. 
4. Go to `OptionSelectorInteractor`. Add the transformation between `Event` and `Output` in the `viewEventToOutput` transformer.
5. Go to `GreetingsContainerInteractor`, and add a branch to the `when` expression in `moreOptionsOutputConsumer`

What we want to do is:
- Take the `Text` that's coming in the `Output`
- Feed it to `HelloWorld` using an Input of `UpdateButtonText`
- Actually go back one screen = manually popping the back stack

This is how it might look:
```kotlin
internal val optionsSelectorOutputConsumer: Consumer<OptionSelector.Output> = Consumer {
    when (it) {
        is Output.OptionSelected -> {
            router.popBackStack()
            helloWorldInputSource.accept(
                UpdateButtonText(it.text)
            )
        }
    }
}
```

## Test run!

At this point we should be able to go to options selection screen, chose an item from the radio group, and pressing the confirm button we should land back at the Hello world! screen with the label of the hello button reflecting our choice.

![](https://i.imgur.com/j9ZDpDN.png)

Press the button! 

![](https://i.imgur.com/8HXzhog.png)


## Reflecting on what we just did: composing

We just created more complex functionality by a composition of individually simple pieces!

When we created our hierarchy like this, we kept the two children decoupled from each other:
```
GreetingsContainer
├── HelloWorld
└── OptionsSelector
```
Even though they work together as a system, `HelloWorld` and `OptionsSelector` has no dependency on each other at all.

This is actually beneficial, because:
- `OptionsSelector` is a generic screen (it renders *whatever* text options we build it with)
- From the perspective of `HelloWorld` it really shouldn't care where it gets its other greetings

Keeping them decoupled means:
- `OptionsSelector` can be reused freely elsewhere to render the same screen with other options
- `HelloWorld` can be reused with different implementation details how to provide more options to it

The combined functionality we just implemented emerges out of the composition of these pieces inside `GreetingsContainer`. Each level handles only its immediate concerns:
- `HelloWorld` implements hello functionality and can ask for more options
- `OptionsSelector` renders options and signals selection
- `GreetingsContainer` connects the dots and contains only coordination logic. All other things are delegated to child screens as implementation details.


## Tutorial complete
 
Congratulations! You can advance to the next one.


## Summary

**Dynamic routing**

1. Make the parent RIB be able to build a child RIB (as seen in **tutorial2**):
    1. Add configuration & routing action in Router
    2. Provide child `Builder` via DI
2. React to some event (usually to child `Output` as seen in **tutorial4**, but can be anything else) in `Interactor` of parent RIB by pushing new configuration to its `Router`
3. Use back press or `popBackStack()` programmatically to go back

**Composing functionality**
1. Instead of one messy RIB, map complex functionality to a composition of simple, single responsibility RIBs
2. When composing, keep parent levels simple. They should only coordinate between child RIBs by `Inputs`/`Outputs`, and delegate actual functionality to children as implementation details.
3. Sibling RIBs on the same level should not depend on each other, so that they can be easily reused elsewhere.
<file_sep>package com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.builder

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial2.rib.greetings_container.GreetingsContainer

class GreetingsContainerBuilder(
    private val dependency: GreetingsContainer.Dependency
) : SimpleBuilder<Node<Nothing>>() {

    override fun build(buildParams: BuildParams<Nothing?>): Node<Nothing> {
        val component = DaggerGreetingsContainerComponent.factory()
            .create(
                dependency = dependency,
                buildParams = buildParams
            )

        return component.node()
    }
}
<file_sep>package com.badoo.ribs.core.customisation

import com.badoo.ribs.core.Rib
import kotlin.reflect.KClass

interface RibCustomisationDirectory {

    val parent: RibCustomisationDirectory?

    fun <T : Rib> getSubDirectory(key: KClass<T>) : RibCustomisationDirectory?

    fun <T : Rib> getSubDirectoryOrSelf(key: KClass<T>) : RibCustomisationDirectory

    fun <T : RibCustomisation> get(key: KClass<T>) : T?

    fun <T : RibCustomisation> getRecursively(key: KClass<T>) : T?

    fun <T : RibCustomisation> getRecursivelyOrDefault(default: T) : T =
        get(default::class) ?: parent?.get(default::class) ?: default
}
<file_sep>package com.badoo.ribs.minimal.reactive

import com.badoo.ribs.minimal.reactive.Cancellable.Companion.cancellableOf


class Relay<T> : Source<T>, Emitter<T> {
    private val listeners: MutableList<(T) -> Unit> = mutableListOf()

    fun accept(value: T) {
        emit(value)
    }

    override fun emit(value: T) {
        listeners.forEach { it.invoke(value) }
    }

    override fun observe(callback: (T) -> Unit): Cancellable {
        listeners += callback
        return cancellableOf { listeners -= callback }
    }
}
<file_sep>package com.badoo.ribs.core.customisation

import com.badoo.ribs.core.Rib
import kotlin.reflect.KClass

open class RibCustomisationDirectoryImpl(
    override val parent: RibCustomisationDirectory? = null
) : MutableRibCustomisationDirectory {

    private val map: MutableMap<Any, Any> = hashMapOf()

    override fun <T : RibCustomisation> put(key: KClass<T>, value: T) {
        map[key] = value
    }

    fun <T : Any> put(vararg values: T) {
        values.forEach {
            map[it::class] = it
        }
    }

    inline operator fun <reified T : Any> T.unaryPlus() {
        put(this)
    }

    override fun <T : RibCustomisation> get(key: KClass<T>): T? =
        map[key] as? T

    override fun <T : RibCustomisation> getRecursively(key: KClass<T>): T? =
        get(key) ?: parent?.get(key)

    override fun <T : Rib> putSubDirectory(key: KClass<T>, value: RibCustomisationDirectory) {
        map[key] = value
    }

    override fun <T : Rib> getSubDirectory(key: KClass<T>): RibCustomisationDirectory?=
        map[key] as? RibCustomisationDirectory

    override fun <T : Rib> getSubDirectoryOrSelf(key: KClass<T>): RibCustomisationDirectory {
        val subDir = map.keys.firstOrNull {
            it is KClass<*> && it.java.isAssignableFrom(key.java)
        }

        return map[subDir] as? RibCustomisationDirectory ?: this
    }

    operator fun KClass<out Rib>.invoke(block: RibCustomisationDirectoryImpl.() -> Unit) {
        if (map.containsKey(this)) {
            // TODO warning for accidental override?
        }
        map[this] = RibCustomisationDirectoryImpl(this@RibCustomisationDirectoryImpl).apply(block)
    }
}
<file_sep>package com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.builder

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.SimpleRoutingChild1
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.SimpleRoutingChild1Node
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.SimpleRoutingChild1Presenter
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.routing.SimpleRoutingChild1ChildBuilders
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.routing.SimpleRoutingChild1Router

class SimpleRoutingChild1Builder(
    private val dependency: SimpleRoutingChild1.Dependency
) : SimpleBuilder<SimpleRoutingChild1>() {

    override fun build(buildParams: BuildParams<Nothing?>): SimpleRoutingChild1 {
        val customisation = buildParams.getOrDefault(SimpleRoutingChild1.Customisation())
        val presenter = SimpleRoutingChild1Presenter(title = dependency.title)
        val router = SimpleRoutingChild1Router(
            buildParams = buildParams,
            builders = SimpleRoutingChild1ChildBuilders(dependency)
        )
        return SimpleRoutingChild1Node(
            buildParams = buildParams,
            viewFactory = customisation.viewFactory(null),
            plugins = listOf(presenter, router)
        )
    }
}
<file_sep>@file:SuppressWarnings("LongParameterList")

package com.badoo.ribs.sandbox.rib.compose_parent

import com.badoo.ribs.builder.SimpleBuilder
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.core.plugin.Plugin

class ComposeParentBuilder(
    private val dependency: ComposeParent.Dependency
) : SimpleBuilder<ComposeParent>() {

    override fun build(buildParams: BuildParams<Nothing?>): ComposeParent {
        val customisation = buildParams.getOrDefault(ComposeParent.Customisation())

        return node(buildParams, customisation)
    }

    private fun node(
        buildParams: BuildParams<*>,
        customisation: ComposeParent.Customisation
    ) = ComposeParentNode(
        buildParams = buildParams,
        viewFactory = customisation.viewFactory(null),
        plugins = emptyList()
    )
}
<file_sep>package com.badoo.ribs.example

import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import coil.DefaultRequestOptions
import coil.ImageLoader
import coil.decode.DataSource
import coil.request.GetRequest
import coil.request.LoadRequest
import coil.request.RequestDisposable
import coil.request.RequestResult
import coil.request.SuccessResult
import com.badoo.ribs.example.test.R

class TestImageLoader : ImageLoader {

    private val instrumentationContext: Context =
            InstrumentationRegistry.getInstrumentation().context

    private val drawable = instrumentationContext.getDrawable(R.drawable.ic_sample)!!

    private val disposable = object : RequestDisposable {
        override val isDisposed = true
        override fun dispose() {}
        override suspend fun await() {}
    }

    override val defaults = DefaultRequestOptions()

    override fun execute(request: LoadRequest): RequestDisposable {
        request.target?.onStart(drawable)
        request.target?.onSuccess(drawable)
        return disposable
    }

    override suspend fun execute(request: GetRequest): RequestResult =
        SuccessResult(drawable, DataSource.MEMORY)

    override fun invalidate(key: String) {}

    override fun clearMemory() {}

    override fun shutdown() {}

}
<file_sep>package com.badoo.ribs.samples.customisations.app

import com.badoo.ribs.core.customisation.RibCustomisationDirectoryImpl
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.SimpleRoutingChild1
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1.SimpleRoutingChild1ViewImpl
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.SimpleRoutingChild1Child1
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child1.SimpleRoutingChild1Child1ViewImpl
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child2.SimpleRoutingChild1Child2
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child1_child2.SimpleRoutingChild1Child2ViewImpl
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.SimpleRoutingChild2
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_child2.SimpleRoutingChild2ViewImpl
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_parent.SimpleRoutingParent
import com.badoo.ribs.samples.simplerouting.rib.simple_routing_parent.SimpleRoutingParentViewImpl

object AppCustomisations : RibCustomisationDirectoryImpl() {

    init {
        +SimpleRoutingParent.Customisation(
            viewFactory = SimpleRoutingParentViewImpl.Factory(R.layout.rib_customisation_parent)
        )
        SimpleRoutingParent::class {
            +SimpleRoutingChild1.Customisation(
                viewFactory = SimpleRoutingChild1ViewImpl.Factory(R.layout.rib_customisation_child1)
            )
            +SimpleRoutingChild2.Customisation(
                viewFactory = SimpleRoutingChild2ViewImpl.Factory(R.layout.rib_customisation_child2)
            )
            +SimpleRoutingChild1Child1.Customisation(
                viewFactory = SimpleRoutingChild1Child1ViewImpl.Factory(R.layout.rib_customisation_child1_child1)
            )
            +SimpleRoutingChild1Child2.Customisation(
                viewFactory = SimpleRoutingChild1Child2ViewImpl.Factory(R.layout.rib_customisation_child1_child2)
            )
        }
    }
}
<file_sep>package com.badoo.ribs.tutorials.tutorial5.rib.option_selector

import androidx.lifecycle.Lifecycle
import com.badoo.mvicore.android.lifecycle.createDestroy
import com.badoo.mvicore.binder.using
import com.badoo.ribs.android.text.Text
import com.badoo.ribs.clienthelper.interactor.Interactor
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelector.Output
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelectorView.Event
import com.badoo.ribs.tutorials.tutorial5.rib.option_selector.OptionSelectorView.ViewModel
import io.reactivex.functions.Consumer

class OptionSelectorInteractor(
    buildParams: BuildParams<Nothing?>,
    private val output: Consumer<Output>,
    options: List<Text>
) : Interactor<OptionSelector, OptionSelectorView>(
    buildParams = buildParams
) {

    private val initialViewModel = ViewModel(
        options
    )

    private val viewEventToOutput: (Event) -> Output = {
        TODO()
    }

    override fun onViewCreated(view: OptionSelectorView, viewLifecycle: Lifecycle) {
        view.accept(initialViewModel)
        viewLifecycle.createDestroy {
            bind(view to output using viewEventToOutput)
        }
    }
}
<file_sep>package com.badoo.ribs.minimal.reactive

fun <T> just(producer: () -> T): Source<T> =
    object : Source<T> {
        override fun observe(callback: (T) -> Unit): Cancellable {
            callback(producer())
            return Cancellable.Empty
        }
    }

fun <A : Any, B : Any, C> combineLatest(source1: Source<A>, source2: Source<B>, combination: (A, B) -> C): Source<C> =
    object : Source<C> {
        /**
         * The internal state (first / second values) should be recreated for each new call to observer
         * Otherwise new subscriptions will not start from empty state but will share values, which is not desirable
         */
        override fun observe(callback: (C) -> Unit): Cancellable {
            var firstValue: A? = null
            var secondValue: B? = null

            fun emitCombined() {
                if (firstValue != null && secondValue != null) {
                    callback(combination(firstValue!!, secondValue!!))
                }
            }

            val cancellable1 = source1.observe {
                firstValue = it
                emitCombined()
            }
            val cancellable2 = source2.observe {
                secondValue = it
                emitCombined()
            }

            return Cancellable.cancellableOf {
                cancellable1.cancel()
                cancellable2.cancel()
            }
        }
    }

fun <T, R> Source<T>.map(transform: (T) -> R): Source<R> =
    object : Source<R> {
        override fun observe(callback: (R) -> Unit): Cancellable =
            this@map.observe { callback(transform(it)) }
    }

fun <T> Source<T>.filter(predicate: (T) -> Boolean): Source<T> =
    object : Source<T> {
        override fun observe(callback: (T) -> Unit): Cancellable =
            this@filter.observe {
                if (predicate(it)) callback(it)
            }
    }

fun <T> Source<T>.startWith(value: T): Source<T> =
    object : Source<T> {
        override fun observe(callback: (T) -> Unit): Cancellable {
            callback(value)
            return this@startWith.observe(callback)
        }
    }
