神刀安全网

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 action 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

First of all, Observable in, Observable out (MVVM does fit into that really well, but it does not really matter).

Why View: Observable <-> Observable ? 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<Void>,    Subscription> signInEnable();   Func1<Observable<Void>,    Subscription> signInDisable();    Func1<Observable<Void>,    Subscription> signInSuccess();   Func1<Observable<Failure>, Subscription> signInFailure(); } 

As you can see, View exposes Observable s and consuming Observable s. 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` {    Subscription bind(SignInView view) {     // …      val subscription = CompositeSubscription()      val login = view.login.share()     val password = view.password.share()      // Boolean is valid/invalid flag.     val credentials = Observable       .combineLatest(login, password,          { l, p -> Triple(l, p, l.isNotEmpty() && p.isNotEmpty()) }       )       .share()      // Actual binding happens here.     subscription += credentials       .filter { it.third }       .map { Unit }       .bind(view.signInEnable)      return subscription;   } } 

What is bind function?

public static <T> Subscription bind(       Observable<T> observable,      Func1<Observable<T>, Subscription> uiFunc) {   return uiFunc.call(observable); } 

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 {    @Override    Func1<Observable<Success>, Subscription> signInSuccess() {     return ui(Success success -> showToast("Success: " + success.details()));   } } 

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

static <T> Func1<Observable<T>, Subscription> ui(Action1<T> action) {     return observable -> observable     .observeOn(mainThread())     .subscribe(action); } 

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()) .

Result

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.

I’m pretty sure you have tons of questions, feel free to ask them here or ping @artem_zin . Thanks for reading, hope you’ll be able to apply it to your projects!

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » RxUi: Talking to Android View layer in a Reactive way

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址