A Great Way to do Presenters

The Cash App Android app uses presenters because they’re easy to write, easy to review, and result in boring code that just works. In this post I’ll show how we use presenters with RxJava, though the approach also works without RxJava.

One common API

Every presenter has the same public API: they extend ObservableTransformer. The presenters have a unique input, a stream of view events, and a unique output, a stream of view models. View events are actions performed by the user, such as tapping a button. View models encapsulate everything necessary to render a view.

Here is how we bind them together:

class MyView(
  val presenter: ObservableTransformer<MyViewEvent, MyViewModel>
) {
  fun onAttachedToWindow() {
    events()
        .compose(presenter)
        .subscribe(::render, ...)
  }
  fun events(): Observable<MyViewEvent> {
    ...
  }
  fun render(viewModel: MyViewModel) {
    ...
  }
}

Top down data flow

Presenters should be easy to write and readable by everyone on the team (including our future selves)! Unidirectional data flow is necessary for readable business logic. Inside each presenter, logic starts at the top with view event emissions and ends at the bottom with view model emissions. The data only goes downwards! Data that goes up the stream is a nightmare to reason about.

When I write a presenter I visualize a waterfall of data and imagine how all those asynchronous sources (view events, database queries, etc.) interact. If we need to query a database to load a view model, that query runs when the stream is subscribed to, not when the presenter is instantiated.

Here’s an example:

class MyPresenter(...) : ObservableTransformer<MyViewEvent, MyViewModel> {
  override fun apply(
    events: Observable<MyViewEvent>
  ): ObservableSource<MyViewModel> {
    return events.publish { events ->
        Observable.merge(
           events.filterIsInstance<BackClick>().handleBackClick(),
           events.filterIsInstance<RecordClick>().handleRecordClick(),
           events.filterIsInstance<SectionClick>().handleSectionClick(),
           database.myData().map { viewModel(it) }
        )
      }
  }
}

Kotlin code is easier to read thanks to operator-like methods. The signature of handleSection is:

private fun Observable<SectionClick>.handleSection(): Observable<MyViewModel>

How do we emit navigation? We don’t! Each constructor has handlers for intents and screen changes and we treat navigation as a side effect:

interface Launcher {
  fun shareText(text: String): Boolean
  fun launchUrl(url: String): Boolean
  fun launchApp(packageName: String): Boolean
}

The presenter has a method for each side effect:

private fun Observable<SectionClick>.handleSection(): Observable<MyViewModel> {
  return doOnNext { event ->
    launcher.launchUrl(event.url)
  }
  .ignoreElements() // We are not emitting view models so we drop the event.
  .toObservable()
}

Testing

The presenters’ simple API makes testing straightforward:

private val launcher = FakeLauncher()
private val events = PublishSubject.create<MyViewEvent>()
private lateinit var viewModels: TestObserver<MyViewModel>

@Before fun setup() {
  val presenter = MyPresenter(launcher)
  viewModels = events.compose(presenter).test()
}

@Test fun sectionClickLaunchesUrl() {
  events.onNext(SectionClick("url"))

  assertThat(launcher.takeNextUrl()).isEqualTo("url")
}

Conclusion

The contract between views and presenters is simple. Each knows what to accept and what to emit. Enforcing unidirectional data flow keeps things easy to understand. Navigation is a side effect. Overall we enjoy a readable uniform code that is easy to test.

Do you like your presenters? how does it compare?