$30 off During Our Annual Pro Sale. View Details »

RxJava: A Stream of Joy and Woe

RxJava: A Stream of Joy and Woe

RxJava has become an invaluable tool to many Android developers, aiding in the composition of the several asynchronous systems we must deal with. However, with so much power RxJava can sometimes be the hammer we hold with almost everything looking like a nail. This talk aims to run through anecdotal examples of where RxJava has worked well, and where it maybe wasn’t the best idea.

Chris Horner

October 27, 2018
Tweet

More Decks by Chris Horner

Other Decks in Technology

Transcript

  1. RxJava
    A Stream of Joy and Woe
    @chris_h_codes

    View Slide

  2. Why have we chased
    the dragon?

    View Slide


  3. View Slide


  4. View Slide


  5. View Slide


  6. View Slide


  7. View Slide

  8. webServices.getSomething()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { something ->
    // ...
    }/

    View Slide

  9. webServices.getSomething()
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { something ->
    // ...
    }/

    View Slide

  10. webServices.getSomething()
    .flatMap { firstResult -> webServices.getSomethingElse(firstResult) }
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { something ->
    // ...
    }/

    View Slide

  11. webServices.getSomething()
    .flatMap { firstResult -> webServices.getSomethingElse(firstResult) }
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe (
    { something ->
    // ...
    },
    { error ->
    // ...
    }
    )

    View Slide

  12. webServices.getSomething()
    .flatMap { firstResult -> webServices.getSomethingElse(firstResult) }
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe (
    { something ->
    // ...
    },
    { error ->
    // ...
    }
    )
    OF
    TERMINAL EVENTS
    BEWARE

    View Slide

  13. val getSomethingFromWeb = webServices.getSomething()
    .flatMap { firstResult -> webServices.getSomethingElse(firstResult) }
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())

    View Slide

  14. systemEventEmitter()
    .switchMap { getSomethingFromWeb.toObservable() }
    .withLatestFrom(somethingElse) {
    // ...
    }

    View Slide

  15. interface WebServices {
    @GET("something")
    fun getSomething(): Single>
    }/

    View Slide

  16. interface WebServices {
    @GET("something")
    fun getSomething(): Single>
    }/

    View Slide

  17. webServices.getSomething()
    .flatMap { firstResult -> webServices.getSomethingElse(firstResult) }
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { something ->
    // ...
    }/

    View Slide

  18. webServices.getSomething()
    .flatMap { firstResult -> webServices.getSomethingElse(firstResult) }
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { something ->
    // ...
    }/

    View Slide

  19. webServices.getSomething()
    .flatMap { firstResult ->
    if (!firstResult.isError()) {
    webServices.getSomethingElse(firstResult.response().body())
    } else {
    Single.just(ERROR)
    }/
    }/
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { something ->
    // ...
    }/

    View Slide

  20. webServices.getSomething()
    .flatMap { firstResult ->
    if (!firstResult.isError() && firstResult.response().isSuccessful) {
    webServices.getSomethingElse(firstResult.response().body())
    } else {
    Single.just(ERROR)
    }/
    }/
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { something ->
    // ...
    }/

    View Slide

  21. webServices.getSomething()
    .flatMap { firstResult ->
    if (!firstResult.isError() && firstResult.response().isSuccessful) {
    webServices.getSomethingElse(firstResult.response().body())
    } else {
    Single.just(ERROR)
    }/
    }/
    .map { result -> transform(result) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { something ->
    // ...
    }/

    View Slide

  22. suspend fun doSomeWork() {
    try {
    val firstResponse = webServices.getSomething().await()
    if (firstResponse.isSuccessful) {
    val something = firstResponse.body()
    val secondResponse = webServices.getSomethingElse(something).await()
    // ..
    }
    } catch (e: IOException) {
    // ..
    }
    }

    View Slide


  23. View Slide

  24. View Slide

  25. • Interfacing with an engine monitoring system
    • Over bluetooth
    • Which is emulating a serial connection
    • Enabling a (very slow) satellite internet service
    • Via SOAP
    • Sending location and engine data over that
    connection
    • Meanwhile monitoring the accelerometer to check
    for accidents

    View Slide

  26. View Slide

  27. Monitoring for accidents

    View Slide

  28. Z
    X

    View Slide

  29. Y

    View Slide

  30. android.hardware.Sensor.TYPE_GRAVITY
    XYZ XYZ XYZ
    XZ XZ XZ
    XYZ
    Check against some threshold
    !
    Throttle detections

    View Slide

  31. sensors.observeSensor(TYPE_GRAVITY)
    .distinctUntilChanged()
    .map { it.sensorEvent.values }
    .filter { it[0] > ROLLOVER_THRESHOLD || it[2] > ROLLOVER_THRESHOLD }
    .map { Emergency.ROLLOVER }
    .throttleFirst(THROTTLE_SECONDS, TimeUnit.SECONDS)
    .observeOn(RealRxSchedulers.mainThread())
    .subscribe { }

    View Slide

  32. • Location updates
    • Bluetooth payloads from engine
    • Managing output sockets as network conditions change
    • Shift status timers

    View Slide

  33. RxPermissions
    .request(Manifest.permission.CAMERA)
    .subscribe { granted ->
    if (granted) {
    // I can control the camera now.
    } else {
    // Oh no, permission denied.
    }
    }

    View Slide

  34. RxActivityResult.on(this)
    .startIntent(takePhoto)
    .subscribe { result, resultCode ->
    if (resultCode == RESULT_OK) {
    result.targetUi().showImage(data)
    } else {
    result.targetUi().printUserCancelled()
    }
    }

    View Slide


  35. View Slide

  36. 13:37
    chris_h_codes

    View Slide

  37. 13:37
    chris_h_codes

    View Slide

  38. 13:37
    chris_h_codes

    View Slide

  39. fun onViewAttached() {
    val user = userStore.getCurrentValue()
    avatarView.setImage(user.profilePic)
    usernameView.text = user.username
    }/

    View Slide

  40. fun onViewAttached() {
    val user = userStore.getCurrentValue()()
    avatarView.setImage(user.profilePic)
    usernameView.text = user.username
    userStore.observe().subscribe { user ->
    avatarView.setImage(user.profilePic)
    usernameView.text = user.usernmame
    }
    }/

    View Slide

  41. https:/
    /github.com/Gridstone/RxStore

    View Slide

  42. https:/
    /github.com/Gridstone/RxStore
    val userStore = storeProvider.valueStore(file, converter)
    userStore.put(user)
    val observeUser: Observable = userStore.observe()
    val putUser: Single = userStore.observePut(user)

    View Slide

  43. webServices.getUser()
    .concatMap { user -> userStore.observePut(user) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())

    View Slide


  44. View Slide

  45. Controller
    View

    View Slide

  46. Controller
    View

    View Slide

  47. State
    Controller
    View

    View Slide

  48. Controller
    View
    State
    Event

    View Slide

  49. Controller
    View
    Observable
    Observable

    View Slide

  50. val events: Observable = Observable.merge(
    loginButton.clicks().map { Event.AttemptLogin(username, password) },
    cancelButton.clicks().map { Event.Cancel }
    )
    https:/
    /github.com/JakeWharton/RxBinding

    View Slide

  51. Observables
    .combineLatest(
    usernameView.textChanges().map { it.isNotEmpty() },
    passwordView.textChanges().map { it.isNotEmpty() })
    { hasUsername, hasPassword -> hasUsername && hasPassword }
    .subscribe(loginButton::setEnabled)
    Username
    Password
    Login

    View Slide

  52. Controller
    View
    Observable
    Observable

    View Slide

  53. View Slide

  54. View Slide

  55. View()
    onFinishInflate()
    onRestoreInstanceState()
    draw()
    display(state)

    View Slide

  56. stateStream
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { state ->
    view.display(state)
    }/

    View Slide

  57. stateStream
    .replay(1)
    .autoConnect()
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { state ->
    view.display(state)
    }/

    View Slide

  58. stateStream
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .replay(1)
    .autoConnect()
    .subscribe { state ->
    view.display(state)
    }/

    View Slide

  59. stateStream

    View Slide

  60. action
    state
    reduce(action, previousState)

    View Slide

  61. Takeaways

    View Slide

  62. Takeaways
    • If you only use Rx for callbacks with nice threading, consider coroutines
    • Beware terminal events
    • Be mindful of thread boundaries and state delivery
    • Don’t try to Rx-AllTheThings

    View Slide

  63. View Slide

  64. RxJava
    A Stream of Joy and Woe
    chris_h_codes
    chris-horner
    chrishorner.codes

    View Slide