Chapters

Hide chapters

Reactive Programming with Kotlin

Second Edition · Android 10 · Kotlin 1.3 · Android Studio 4.0

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Operators & Best Practices

Section 2: 7 chapters
Show chapters Hide chapters

17. RxBindings
Written by Alex Sullivan

In the last chapter, you learned all about wrapping existing APIs to make them into Observables. Hopefully, you’ve realized how powerful it is to express a lot of the framework APIs in reactive terms. Unfortunately, it’s a fair amount of repetitive work to wrap all of these frameworks.

It’s not too bad to make a reactive extension for, say, a Button. And it’s not too bad to make a reactive extension for an EditText. But, as you keep going, it starts to become a bit laborious to keep making these reactive wrappers.

There’s an extremely handy library called RxBindings, which takes care of making reactive bindings for all of the Android view classes. So good news! You get to be lazy and rely on a library to make those extensions for you. And as we all know, programming is 1% creativity and 99% laziness.

In this chapter, you’ll revisit the HexColor app and improve on it by using the RxBindings library.

Getting started

Open the starter project and run the app. You should see the HexColor app from Chapter 15, “Testing RxJava Code.”

Feel free to tap around. You can type in a hex code, and the background will change to that color. It will also show the RGB value and if you type in one of the colors in the ColorName enum, the name will show, too.

There’s a few limitations to the app, though. First off, most colors you enter don’t have an associated color name in the ColorName enum. You can see this list in X.kt.

That’s a hard nut to crack, since there’s a near infinite number of color combinations you can use in the app. Next up, manually tapping the digits can be a bit burdensome. It’d be nice if you could also use the keyboard to enter a new hex color.

In this chapter, you’ll work through solving both of these problems while also using the RxBinding library to make the Android view components a bit more reactive.

Extending ValueAnimator to be reactive

Speaking of making things more reactive, take a look at the animateColorChange method in ColorActivity. It’s the method that’s responsible for that fancy color changing animation. It’s a pretty great method, but it’s not very reactive. In the spirit of building on the work you did last chapter, you’re going to wrap that call in a reactive wrapper to make it fit better with the rest of the reactive app.

Open the AnimationUtils.kt file and look at the colorAnimator method:

fun colorAnimator(fromColor: Int, toColor: Int): Observable<Int> {
  return Observable.empty()
}

colorAnimator takes two arguments: an integer named fromColor representing the starting color and another integer named toColor representing the ending color. The idea, here, is to convert the animateColorChange method to use this colorAnimator Observable instead of using a ValueAnimator the way it does now.

Replace the return Observable.empty() line with the following:

// 1
val valueAnimator =
  ValueAnimator.ofObject(ArgbEvaluator(), fromColor, toColor)
valueAnimator.duration = 250 // milliseconds
// 2
val observable = Observable.create<Int> { emitter ->
  // 3
  valueAnimator.addUpdateListener {
    emitter.onNext(it.animatedValue as Int)
  }
}

Here’s a breakdown of the above code:

  1. Create a new ValueAnimator using the ArgbEvaluator to go from the fromColor to the toColor. In case you’re not familiar with ValueAnimator, it provides a handy way to get interpolated values between two values you provide, that you can later use to animate some view between them. The ArgbEvaluator interpolates two color Ints to make a smooth transition.

  2. Create a new Observable via the create function. The Observable will emit Ints since that’s what type the ValueAnimator declared above will output.

  3. Add an updateListener to the valueAnimator and call emitter.onNext with the updated animated value. Now whenever the valueAnimator object calls its update listener with a new value, the Observable will emit that value.

Now, add the following line to finish up the reactive wrapper:

return observable.doOnSubscribe { valueAnimator.start() }

You’re returning the observable object you created earlier. You’re using the doOnSubscribe operator to actually start the valueAnimator. Now whenever someone subscribes to the Observable the valueAnimator will start emitting.

Nifty.

Head over to ColorActivity.kt and replace the animateColorChange method with the following:

private fun animateColorChange(newColor: Int) {
  val colorFrom = root_layout.background as ColorDrawable
  colorAnimator(colorFrom.color, newColor)
    .subscribe { color ->
      root_layout.setBackgroundColor(color)
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        window.statusBarColor = color
      }
    }
    .addTo(disposables)
}

Now, instead of directly using a valueAnimator, you’re using the colorAnimator method you defined earlier and subscribing to the resulting Observable.

Now that this animation is represented via an Observable, you could easily chain together multiple animators and utilize the power of Rx.

Using RxBindings with Android widgets

Now that you’ve react-ified that animation code, it’s time to move on to actually using RxBindings.

First off, open the app’s build.gradle file (not the top-level one!).

Navigate to the dependencies section and take a look at the RxBindings dependencies:

implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0'

Open ColorViewModel.kt class. If you look at the structure of the class, you’ll notice that there’s two distinct “sections” to the class:

  1. There’s the init block, which is where the bulk of the actual logic is. This is where all of the Rx magic happens. It’s declarative and simple to read. It’s not stateful, and it’s cohesive.
  2. There’s the several xClicked methods below the init block. These methods are primarily boilerplate to forward relevant data into the hexStringSubject.

If you think about it, these xClicked methods are acting as intermediaries for a stream-like flow. Take the clearClicked method for example. At any point in time, the app is listening for the user to click the Clear button. That event then triggers a call to clearClicked, which then maps that Clear-clicked event into a new value for the hexStringSubject.

Converting clearClicked() to use RxBindings

The clearClicked method is really just an impediment to the above flow. What the app really needs is another Observable<Unit> that represents the user clicking the Clear button — RxBindings provides that functionality, but there’s a catch.

Update the ColorViewModel class header to add a clearStream Observable:

class ColorViewModel(
  backgroundScheduler: Scheduler,
  mainScheduler: Scheduler,
  colorCoordinator: ColorCoordinator,
  clearStream: Observable<Unit>
) : ViewModel() { ... }

Now, the ColorViewModel takes a clearStream object of type Observable<Unit>. The type of clearStream is Unit because the app doesn’t actually care what the value emitted by the Observable is — all it cares about is that the Observable emitted something.

Head over to ColorActivity. If you look at the line declaring viewModel, you’ll see that the ColorViewmodel is being instantiated inside a ViewModelProvider.NewInstanceFactory object. There’s a fair amount of boilerplate here that isn’t important. Update the actual line returning a ColorViewModel:

return ColorViewModel(
  Schedulers.io(),
  AndroidSchedulers.mainThread(),
  ColorCoordinator(),
  clear.clicks()
) as T

There’s only one thing different here: You’re passing one extra argument — clear.clicks(). clear is a reference to the big X button that clears the current color in the app. The app is using the Kotlin Android Extensions plugin to automatically generate view references, so no more findViewById boilerplate. clicks() is an extension method on View provided by RxBindings that turns a views click listener into an Observable<Unit>. It’s that easy to get an Observable of click events using RxBindings. Isn’t that magical?!

Remove the block of code setting a click listener on the clear view. Since you’ll handle clear events via an Observable, you don’t need to worry about setting a click listener on it anymore.

Back in ColorViewModel, you can also remove the clearClicked method. Again, you’ll be handling clear events via an Observable, so it’s unnecessary.

Now that all the plumbing is in place, it’s time to actually utilize the clearStream to clear out the current color.

In the bottom of the init method, add the following:

clearStream
  .map { "#" }
  .subscribe(hexStringSubject::onNext)
  .addTo(disposables)

The code is dead simple: You’re subscribing to the clearStream Observable and mapping each Unit event to the "#" string. Then you’re forwarding that string to the hexStringSubject object using its onNext method. You’re using a method reference to make the code nice and compact.

Notice that this code is almost identical to the code in clearClicked. Nothing is fundamentally changing, you’re just consolidating the code and Rx-ifying the app!

Run the app. Enter your favorite color string (I know you’ve got favorites!) and click the clear button. It should be cleared.

Dangerzone!

There’s actually a subtle but devious bug in the code you just wrote. To demonstrate the bug, run the app and then rotate the device. Input a hex string and hit the clear button. You’ll notice that nothing happens - the color isn’t cleared.

RxBindings works by generating a series of convenient helper functions on a plethora of different Views. However, when the app is rotated the actual View is destroyed and a new set of Views are created. But the ViewModel you’re using survives the configuration change - that’s the whole point of the ViewModel class!

That means that ColorViewModel is now holding onto a reference for an Observable that’s firing for a View that no longer exists! Since the old clear button has been destroyed and there’s a brand new clear button, RxBindings doesn’t have a reference to the new button. That means you won’t get any of the click effects that you’d expect.

Working around the issue

You can’t just pass in an Observable generated by RxBindings into your ViewModel via the constructor, but you can emulate that reactive flow.

First, you need to go back a few steps. Remove clearStream: Observable<Unit> from the ColorViewModel constructor and in ColorActivity, delete clear.clicks() from the parameter list when creating the viewModel object.

Then, start fixing the problem by adding a new property to the top of ColorViewModel:

private val clearStream = PublishSubject.create<Unit>()

Now add back in the clearClicked method and trigger the new clearStream subject:

fun clearClicked() = clearStream.onNext(Unit)

You’re now building up your own clear clicked Observable without the bugs discovered earlier. The last thing you need to do is trigger the clearClicked method. You can use RxBindings in your ColorActivity to keep things nice and reactive. Add the following below the viewModel creation block:

clear.clicks().subscribe { viewModel.clearClicked() }
    .addTo(disposables)

You’re now getting all of the benefits of reactive views without the bug previously discovered.

Converting backClicked() to use RxBindings

Now that you’ve handled the Clear button, you’re going to go through the same process for the Back button. Update the ColorViewModel with another PublishSubject to represent back clicks:

private val backStream = PublishSubject.create<Unit>()

And update the backClicked method to forward a value into backStream

fun backClicked() = backStream.onNext(Unit)

Finally, start listening to actual click events on the back button with RxBindings in ColorActivity:

back.clicks().subscribe { viewModel.backClicked() }
    .addTo(disposables)

Take a look at the old implementation of the backClicked method:

fun backClicked() {
    if (currentHexValue().length >= 2) {
        hexStringSubject.onNext(currentHexValue()
            .substring(0, currentHexValue().lastIndex))
    }
}

This method is a bit more complicated than the clearClicked method you replaced earlier. If the current hex value has a length greater than two — i.e., it’s more than just the "#" string — then you want to add a new string onto the hexStringSubject. That new string is whatever the current string is minus the last character.

Add the following code at the bottom of the classes init block in ColorViewModel:

// 1
backStream
  // 2
  .map { currentHexValue() }
  // 3
  .filter { it.length >= 2 }
  // 4
  .map { it.substring(0, currentHexValue().lastIndex) }
  // 5
  .subscribe(hexStringSubject::onNext)
  .addTo(disposables)

Reactive programming is so different from imperative programming that it may be a good idea to break the above code down:

  1. You’re subscribing to the backStream Observable, which is an Observable<Unit>. Every time the user clicks the Back button, this Observable will emit a Unit value.
  2. You don’t actually care about the Unit value emitted by the Observable. You’re just using it as a trigger. So you’re immediately mapping that Unit object to the currentHexValue(), which you’ll use later on in the stream.
  3. You only want to proceed through the chain if the current hex values length is greater than or equal to two.
  4. Next up you’re getting a substring of the current hex value starting at zero and going up to, but not including, whatever the last index of the hexstring is. The last index is just the size of the string minus 1. Since you used the filter operator above, you can be confident that this string will have a length >= 2.
  5. You’re subscribing to the Observable and forwarding the emitted string to the hexStringSubject, so the rest of the code above this block can react to the new hex string value.

Note: You may be tempted to avoid the first map call and instead just operate directly on whatever currentHexValue() provides in both the filter operator and the second map operator. While that may be tempting, it would also introduce a race condition and a potential crash! Between the first filter being executed and the second map being executed, another thread could update the current hex value and your assumption that the length of the string returned by currentHexValue() being greater than two is no longer certain. Chances are it wouldn’t happen in this app, but it’s always worth keeping those potential race conditions in mind.

Boom! You’ve replicated the code in backClicked in a more streamlined reactive style. Run the app. Tapping the Back key should work exactly the same.

Last but not least is the digitClicked method.

Converting digitClicked() to use RxBindings

Again, add a new subject representing digit clicks in the ColorViewModel class:

private val digitsStream = BehaviorSubject.create<String>()

This time you’re using a BehaviorSubject so that every time you subscribe to the stream you’ll get the latest and greatest digit clicks.

The incoming digits will be of type String. Each String will be a single character.

In ColorActivity, delete the line declaring digits and the forEach block setting click listeners on each digit.

Then, replece the digits declaration and the following digits.forEach with the following snippet:

// 1
val digits = listOf(zero, one, two, three, four, five, six,
  seven, eight, nine, A, B, C, D, E, F)
  // 2
  .map { digit ->
    // 3
    digit.clicks().map { digit.text.toString() }
  }

If the above is confusing, don’t worry! Here’s a breakdown:

  1. Build up a list of each digit on the “keypad” in the app. Each object in this list is a TextView. Again, the app is using the Kotlin Android Extensions to provide easy reference to each view in the app. So this listOf() call returns a List<TextView>.
  2. Call map on this list. digit in the lambda block is a TextView.
  3. Call clicks() on each digit to turn it into an Observable<Unit>. Then, call a map on that Observable. Map the Unit value to the string representation of the text in the digit TextView. Don’t be confused by the two maps — one is on the List<TextView>, the other is on the Observable<Unit>.

The result of the above code is that digits is now a List<Observable<String>>. How meta is that?!

Now, add the following below digits:

val digitStreams = Observable.merge(digits)

You’re using the merge method you learned about in Chapter 9, “Combining Operators,” to combine the List<Observable<String>> into a single Observable<String>. Now, any time a user taps one of the digits in HexColor digitStreams will emit.

Next, update the digitClicked method to forward your digit through to your subject:

fun digitClicked(digit: String) = digitsStream.onNext(digit)

Finally, subscribe to the digitStream stream you just created in ColorActivity and forward the result through to the ColorViewModel:

digitStreams.subscribe(viewModel::digitClicked)
    .addTo(disposables)

You’re using a method reference to make the code even more concise.

Back in ColorViewModel, add the following at the bottom of the init file:

digitsStream
  // 1
  .map { it to currentHexValue() }
  // 2
  .filter { it.second.length < 7 }
  // 3
  .map { it.second + it.first }
  .subscribe(hexStringSubject::onNext)
  .addTo(disposables)

Here’s another breakdown:

  1. Take the String emitted by digitsStream and use map to combine it with whatever the current hex value is. The to infix function is a simple shorthand to create a Pair object.
  2. Use filter to ignore any element emitted while the current hex values length is ≥ 7. If the current hex value is ≥ 7, you want to ignore any taps, since the full hex color string has already been input.
  3. Now, combine the current hex value and the String the user just tapped, appending the new string onto the existing hex value.

Delete the old digitClicked method and run the app.

Now, take a step back and look at the ColorViewModel class. Doesn’t it look fantastic? So sleek and declarative. Just a real beauty. Don’t you wish all code could be this declarative and reactive?

Fetching colors from an API

The code for the app is looking a lot better. But the app itself is still fairly limited; it can only display names for a small list of colors. You’re going to change that by integrating with the color API found here: www.thecolorapi.com.

Open the ColorService class and add the following method to the bottom of the interface outside the companion object:

@GET("id")
fun getColor(@Query("hex") hex: String): Single<ColorResponse>

The above code will fetch a lot of metadata for a color based off the hex string passed in.

Now add another constructor parameter at the bottom of the list of constructor parameters for the ColorViewModel class:

colorApi: ColorApi

In the ColorActivity, pass in ColorApi to the ColorViewModel constructor:

return ColorViewModel(
  Schedulers.io(), 
  AndroidSchedulers.mainThread(),
  ColorCoordinator(),
  ColorApi
) as T

Now, open up ColorApi.kt. ColorApi is a simple object that wraps the ColorService class to make API calls. Replace the body of the getClosestColor method with the following:

return colorService.getColor(hexString)

Now, head back to ColorViewModel.kt. It’s time to actually use the API.

Find the Observable chain that searches through the ColorName enum whenever a new hex string comes in. It looks like this:

hexStringSubject
  .subscribeOn(backgroundScheduler)
  .observeOn(mainScheduler)
  .filter { hexString -> ColorName.values()
    .map { it.hex }
    .contains(hexString) }
  .map { hexString -> ColorName.values()
    .first { it.hex == hexString } }
  .map { it.toString() }
  .subscribe(colorNameLiveData::postValue)
  .addTo(disposables)

Delete the whole chain and replace it with the following:

hexStringSubject
  .filter { it.length == 7 }
  .observeOn(mainScheduler)
  .flatMapSingle {
    colorApi.getClosestColor(it)
        .subscribeOn(backgroundScheduler)
  }
  .map { it.name.value }
  .subscribe(colorNameLiveData::postValue)
  .addTo(disposables)

You’re again filtering out any hex strings that aren’t yet at size seven. You’re then using flatMap to fetch the color details from the color API making sure to subscribe to the network call off the main thread. Then you’re using map to convert the ColorResponse object you get back from the API into a human readable color name. Finally you’re posting that value to the colorNameLiveData.

Run the app. Try out any color combination, and you’ll see a name. How cool is that?

My favorite is #555555. It makes me feel like a boss.

Displaying an information dialog

Next up on the docket is to allow the user to manually type out a color string without tapping the digits on the app. To do this, you’re going to expose a bottom sheet dialog that includes an EditText widget that the user can input text in.

Add the following to the bottom of the onCreate method in ColorActivity:

color_name.clicks()
  .subscribe {
    val bottomSheetDialog =
      ColorBottomSheet.newInstance(hex.text.toString())
    bottomSheetDialog
        .show(supportFragmentManager, "Custom Bottom Sheet")
  }
  .addTo(disposables)

You’re using the clicks extension method on the color_name widget to create an Observable<Unit> representing clicks. In the subscribe block you’re creating an instance of the ColorBottomSheet fragment with the current hex color string value and showing it.

Run the app and input a color. If you click on the color name in the top-right, you should see a bottom dialog with an empty EditText appear.

Open up the ColorBottomSheetViewModel class. It’s pretty empty right now. It has three LiveData objects — one for showing a loading indicator, one for showing the name of the color input by the user, and a last one for showing the “closest” matching color to whatever color the user inputs.

Note: The concept of a difference or distance between two colors is actually really interesting! You can use the distance formula you learned in algebra to get the “distance” between two colors. Wikipedia has a great article about it: https://en.wikipedia.org/wiki/Color_difference.

Before you can start using the color API in this new bottom sheet you need to access the string that the user types into the EditText at the top of the bottom sheet.

Normally, the way you’d listen for text changes on an EditText would be to create a new TextWatcher object and implement the afterTextChanged event. But that’s a lot of boilerplate. You’ll use RxBindings instead to get an Observable<String> representing the text changes.

First, update the ColorBottomSheetViewModel to have a val representing search strings. Add the following to the top of the class:

private val searchObservable = BehaviorSubject.create<String>()

And just like before add a method that will be called to forward a value into your new Observable:

fun onTextChange(text: String) = searchObservable.onNext(text)

Next, head to ColorBottomSheet and add the following below the line declaring the viewModel in onViewCreated:

hex_input.textChanges()
  .map { it.toString() }
  .subscribe { viewModel.onTextChange(it) }

textChanges is an RxBindings extension method on TextView. It turns an Observable<Editable>, so you’re using map to convert the Editable into a String. You’re then subscribing to the resulting Observable and forwarding it through to your ViewModel.

Since you’re using the subscribe method, you need to make sure to dispose of the resulting Disposable at some point. Add a CompositeDisposable value at the top of the file:

private val disposables = CompositeDisposable()

Update the textChanges block you just wrote to add the Disposable into your newly created disposables object:

hex_input.textChanges()
    .map { it.toString() }
    .subscribe { viewModel.onTextChange(it) }
    .addTo(disposables)

You’re now ready to use the text-changes Observable to fetch a color from the color API.

Head back to ColorBottomSheetViewModel and create an init block with the following code:

init {
  val colorObservable = searchObservable
    .filter { it.length == 7 }
    .flatMapSingle {
      ColorApi.getClosestColor(it).subscribeOn(Schedulers.io())
    }
    .map { it.name }
    .share()
}

Above:

  • You’re using the Observable<String> passed in from ColorBottomSheet, which you got via the RxBindings library, to construct a new Observable chain. It first filters out any inputs that aren’t of length seven, since that’s the correct length for a hex color string.
  • Then you’re using flatMapSingle to fetch the closest color to the input string from the color api, which returns a Single with the network response.
  • Finally, you’re using map to pull out the ColorName object from the ColorResponse.
  • You’re using the share operator to share the whole chain so you can have multiple subscribers listen to the same set of results.

The above block is great, but you’re still not actually subscribing to the colorObservable or emitting anything into the LiveData objects. Add the following below the colorObservable chain:

colorObservable
  .subscribe { colorNameLiveData.postValue(it.value) }
  .addTo(disposables)

That’s more like it! You’re subscribing to colorObservable and pulling out the value object from the ColorName class, which maps to the name of the color, and pushing it through the colorNameLiveData object.

Before you run the app, finish off the functionality of the bottom sheet by adding one more subscriber:

colorObservable
  .subscribe {
    closestColorLiveData.postValue(it.closest_named_hex)
  }
  .addTo(disposables)

You’re now pulling out the closest_named_hex value from the ColorName object and pushing it through the closestColorLiveData object. Nice!

Note: closest_named_hex is named with underscores rather than the normal camelCase format to allow for automagical json deserializing from the GSON deserialization library.

Run the app, enter a color and tap the color name to show the bottom sheet. Input a hex value and you should see the name of the closest named color the API could find, as well as the hex value of that closest color. You may need to close the keyboard to see the output.

The app is almost perfect. The only issue is if you enter a value on the main screen and then click the color name you don’t see the details of the color that you input. You only see the closest color and the name of that color if you type in something new. This is an easy one line fix.

In ColorBottomSheetViewModel utilize the startsWithItem operator before the filter operator in the colorObservable declaration (first chain):

.startWithItem(startingColor)

Now, run the app and tap around. You should see the details of whatever color you originally input on the bottom sheet when it first appears.

Challenges

Challenge 1

Start from the final project from this chapter and update the bottom color sheet to show a loading indicator while the ColorBottomSheetViewModel is loading a color from the color API.

You don’t need to worry about adding a new view or hooking up a new live data. You can use the showLoadingLiveData to toggle whether the loading indicator should be shown or hidden.

Challenge 2

Update the ColorBottomSheet so that the EditText input always includes a # character, is limited to seven characters, and only allows characters between 1-9 and A-F.

To accomplish this challenge, you’ll want to use another RxBindings method on EditText, specifically the afterTextChangesEvents method. afterTextChangesEvents produces an Observable<TextViewAfterTextChangeEvent>. TextViewAfterTextChangeEvent includes an Editable object that you can manipulate to only include the strings that you want.

If you’re having difficulties, take a look at the completed challenges project for a hint.

Key points

  • Practicing creating reactive extensions around existing Android classes.
  • Using the RxBindings library to create reactive streams from Android widgets.
  • Using the clicks extension method to replace an Android click listener.
  • Using the textChanges extension method to get a stream of TextView or EditText changes.
  • Using the afterTextChangeEvents method to get a stream describing any changes that are happening to an EditText.

Where to go from here?

If you’re hooked on RxJava, RxBindings is a great supplement to the regular classes. RxBinding is simple to use, provides a consistent API for consumption, and makes your application much more composable and reactive.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.