Reactive Programming 102

Overview

  • Usage examples
  • Pitfalls
  • Operator of the week

How we use Rx

Autosuggest

autosuggestInput
    .throttleLast(250, TimeUnit.MILLISECONDS)
    .distinctUntilChanged()
    .switchMap(input -> {
        return autosuggestManager.autosuggest(input);
    })
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread)
    .subscribe(results -> ...);

Recent Searches & Price Alerts

Observable.zip(
    // Get recent searches
    recentSearchStore.getRecentSearches(),
    // Get Price Alerts
    priceAlertStore.getPriceAlerts(),
    // Get latest saved search configuration
    searchConfigStore.latestSearchConfig(),
    (searches, alerts, lastConfig) -> {
        // Batch the data based on common City
        ...
    })
    .flatMap(batched -> {
        // Get images for the cities
        return Observable.zip(
            Observable.just(batched),
            imageStore.getCityImage(batched.cityId()),
            (ars, s) -> ars.setImageUrl(s));
    });

User-vector upload

userVector
    // Avoid uploading too often
    .throttleLast(3, TimeUnit.SECONDS)
    // Upload
    .flatMap(it -> userVectorStore.upload(it))
    // Re-subscribe on failure
    .retry()
    // Upload on background thread
    .subscribeOn(Schedulers.newThread())
    // Don't care about the results
    .subscribe();

+

  • Boilerplate code--
  • Code quality++
  • Active Rx community
  • Common, de-facto pattern for:
    • Composing tasks
    • Cancelling tasks
    • Handling task errors
    • Handling threading

-

  • Learning curve is steeper than it seems
  • Debugging difficulties++
  • Cargo-cult coding is really dangerous with Rx

Pitfalls

Observable.create()

  • You need to handle everything -> gotta be careful
  • No backpressure-support
  • No automatic unsubscription-support
  • If possible, use .just() / .from() / .defer() instead
Observable.defer(() -> Observable.just(expensiveCalculation()))
                .subscribe(System.out::println);

Unsubscription

  • Only best-effort!
  • After you unsubscription some events might pop in!
  • Depends on the setup of the stream
    • The operators used
    • The threading environment
  • It's often not an issue - but be on the lookout

Error blocks

  • You can skip the error handling block on subscription
Observable.range(0, 10)
    .doOnNext(integer -> {
        if (integer.equals(9)) throw new RuntimeException("Surprise!");
    })
    .subscribe(System.out::println); // -> exploding at 9 :(
  • You can easily skip it even when it's needed
  • Tip: Use full Subscribers instead of Actions only

Endless streams

  • 0...n onNext() -> onComplete() | onError()
  • Endless steams do heave their place
    • E.g. touch input, sensor input, etc.
  • Keep in mind that certain operators go crazy with them
    • E.g. concatMap()

Unsafe usage

  • RxJava is heavily optimized for multi-threaded usage
  • Unsafe is being used
    • RxJava uses lock-free structures from JCTools 
    • E.g. MPSC and SPSC arrays of various flavors
    • Unsafe provides CAS operations on primitive types
  • Samsung - Android 5.0, 5.0.1, 5.0.2 -> crash :(
  • We can switch it off: If Samsung -> No Unsafe
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP && 
    "samsung".equalsIgnoreCase(Build.MANUFACTURER)) {
    System.setProperty("rx.unsafe-disable", "True");
}

Operators

  • RxJava can result in really short, concise code
  • It's not free though -> complexity
  • Don't just assume, test!
  • Unit test, unit test, unit test
  • Especially for more complex streams

Lambda hell

  • Look out for readability
  • For complex streams, you might not want to inline everything
  • Especially without lambdas! (< Java 1.8)
  • What may help here:
    • >= Java 1.8
    • Retrolambda
    • Kotlin

Operator of the week:

*Map()

map()

  • Convert A -> B
  • Converts via a simple function
B convert(A input){ ... }
  • Synchronous
  • Keeps ordering

flatMap()

  • Convert A -> B
  • Converts via an Observable!
Observable<B> convert(A input){ ... }
  • Sync OR async depending on the converting Observable
  • 'Flat' -> flattens out the results of the converting Observable to the main stream
  • You don't care about the ordering of the output events!

concatMap()

  • Convert A -> B
  • Converts via an Observable!
Observable<B> convert(A input){ ... }
  • Sync OR async depending on the converting Observable
  • Inner observables must complete
  • The ordering of output events follows the ordering of input events

switchMap()

  • Convert A -> B
  • Converts via an Observable!
Observable<B> convert(A input){ ... }
  • Sync OR async depending on the converting Observable
  • Keeps 'ordering': Only outputs the events of the most recent inner Observable!

The end!

Reactive Programming I.

By rzsombor

Reactive Programming I.

  • 716