RxUi: Talking to Android View layer in a Reactive way

Hello dear reader, TL;TR: View layer: Observable <-> Observable + tiny RxUi library.

A lot of Android apps nowadays use RxJava as a foundation of the control flow for business logic. That's great. But what if you go further and apply it everywhere, literally everywhere, including View layer?

No matter what fancy pattern you use (MVP, MVVM, etc) you can talk to your View layer in a Reactive way, and no matter what pattern you use you will face 3 main problems.

1) "Leaking" Main Thread to Presenter/ViewModel/etc layers.

Tired of @Inject @MainScheduler Scheduler mainScheduler in every Presenter/ViewModel/etc and then passing Schedulers.immediate() in tests? Me yes.

There are no significant reasons why it should not be part of View layer implementation.

2) Action posted to View layer should be part of Subscription.

Why? To be able to unsubscribe() and stop all posted UI actions safely. Simple view.post(() -> …) is not an option.

3) Backpressure occurred in the View layer should be detected on Presenter/ViewModel/etc layer.

Backpressure happens (very rare), it's okay. You just need to be able to handle it and since View layer should be as dumb as possible you should be able to handle backpressure on the layer above it.

Functional Concept of the Solution


View layer: Observable <-> Observable

Why? Because it's just a beautiful concept as is and it'll help us to solve problems described above ^.

// Observable <-> Observable.
interface SignIn {  
  // Produces.
  Observable<String> login();
  Observable<String> password();
  Observable<Void>   signInClicks();

  // Consumes.
  Func1<Observable<Boolean>, Subscription>      signInEnable();
  Func1<Observable<SignInResult>, Subscription> signInResult();

As you can see, View exposes Observables and consuming Observables. That's functional purity of the concept.

You can break complex consumer functions into small ones, if you'll follow the idea that "View should be as dumb as possible" to maximum you will probably end up having tons of separate functions to display small pieces of information like signInSuccess() and signInFailure() instead of signInResult(). Totally up to you.

Presenter/ViewModel/etc layer

Ok, now let's see how to talk to View layer that has Observable <-> Observable API?

// Sorry for Kotlin here and Java above, trying to keep article short.
class `SignIn Presenter or ViewModel / etc` {

  fun bind(view: SignInView): Subscription {
    val subscription = CompositeSubscription()

    // …

    val login = view.login.share()
    val password = view.password.share()

    // Boolean is valid/invalid flag.
    subscription += Observable
      .combineLatest(login, password, 
        { l, p -> l.isNotEmpty() && p.isNotEmpty() }
      .bind(view.signInEnable) // Here is the binding.

    // …

    return subscription

What is bind function?

fun <T> bind(o: Observable<T>, uiFunc: Func1<Observable<T>, Subscription>): Subscription {  
    return uiFunc.call(o)

Very simple and reusable in every Presenter/ViewModel/etc. Again that's a beautifulness and functional purity of this concept.

Unfortunately, it's not that readable in Java, but still works!

View layer again

On the view layer we need to actually process the Observable.

class `Fragment or android.view.View / etc` implements SignInView {

  Func1<Observable<Boolean>, Subscription> signInEnable() {
    return ui(Boolean enable -> signInButton.setEnabled(enable)))

And ui function is simple function that applies observeOn(mainThread()) and binds Func1 to Observable.

fun <T> ui(action: Action1<T>): Func1<Observable<T>, Subscription> {  
  return (observable) -> observable
Tests for Presenter/ViewModel/etc

In tests you no longer need to pass main thread scheduler, just apply function similar to ui() but without observeOn().

testUi() is a function, very similar to ui() but it does not apply observeOn(mainThread()).


Pure functional concept of Rx interaction with UI where:

  1. Main Thread is an implementation detail of View layer.
  2. Any action posted on UI can be canceled via subscription.unsubscribe().
  3. Backpressure can be detected and handled on layer above View.

You will need only 2 functions to make it work:

  1. static <T> Subscription bind(Observable<T> observable, Func1<Observable<T>, Subscription> func)
  2. static <T> Func1<Observable<T>, Subscription> ui(Action1<T> action)

And one for testing: static <T> Func1<Observable<T>, Subscription> testUi(Action1<T> action)

RxUi library and samples in Java and Kotlin

I've created a veeery tiny library (2 functions) for that so you won't need to write them yourself: RxUi it also has Kotlin extension to simply write Observable.bind() and test module for your tests.

BTW, RxUi works really well with RxBinding from Jake Wharton, but it's totally optional and up to you.

Thanks for reading, hope you'll be able to apply it to your projects!

Thanks for discussions to Igor Korotenko, also he has an idea of architecture concept which might be build on top of this one, probably we'll see it in some future.

comments powered by Disqus