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

7. Transforming Operators
Written by Alex Sullivan & Scott Gardner

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Before you decided to buy this book and commit to learning RxJava, you might have felt that RxJava was some esoteric library; elusive, yet strangely compelling you to master it. And maybe that reminds you of when you first started learning Android or Kotlin. Now that you’re up to Chapter 7, you’ve come to realize that RxJava isn’t magic. It’s a carefully constructed API that does a lot of heavy lifting for you and streamlines your code. You should be feeling good about what you’ve learned so far.

In this chapter, you’re going to learn about one of the most important categories of operators in RxJava: transforming operators. You’ll use transforming operators all the time, to prepare data coming from an Observable for use by your Subscriber. Once again, there are parallels between transforming operators in RxJava and the Kotlin standard library, such as map() and flatMap(). By the end of this chapter, you’ll be transforming everything!

Getting started

This chapter will use a normal IntelliJ project, so go ahead and open the starter project now.

Transforming elements

Observables emit elements individually, but you will frequently want to work with collections. One typical use case is when you’re emitting a list of items to show in a RecyclerView.

exampleOf("toList") {

  val subscriptions = CompositeDisposable()
  // 1
  val items = Observable.just("A", "B", "C")

  subscriptions.add(
      items
          // 2
          .toList()
          .subscribeBy {
            println(it)
          }
  )
}
--- Example of: toList ---
[A, B, C]

map operator

RxJava’s map operator works just like Kotlin’s standard map function, except it operates on observables instead of a collection. In the marble diagram, map takes a lambda that multiplies each element by 2.

exampleOf("map") {

  val subscriptions = CompositeDisposable()

  subscriptions.add(
      // 1
      Observable.just("M", "C", "V", "I")
          // 2
          .map {
            // 3
            it.romanNumeralIntValue()
          }
          // 4
          .subscribeBy {
            println(it)
          })
}
--- Example of: map ---
1000
100
5
1

Transforming inner observables

You may have wondered at some point, “How do I work with observables that are properties of observables?” Get ready to get your mind blown.

class Student(val score: BehaviorSubject<Int>)

flatMap operator

The first one you’ll learn about is flatMap. The documentation for flatMap states that it “Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence.” Makes perfect sense, right?

exampleOf("flatMap") {

  val subscriptions = CompositeDisposable()
  // 1
  val ryan = Student(BehaviorSubject.createDefault(80))
  val charlotte = Student(BehaviorSubject.createDefault(90))
  // 2
  val student = PublishSubject.create<Student>()

  student
      // 3
      .flatMap { it.score }
      // 4
      .subscribe { println(it) }
      .addTo(subscriptions)
}
student.onNext(ryan)
--- Example of: flatMap ---
80
ryan.score.onNext(85)
--- Example of: flatMap ---
80
85
student.onNext(charlotte)
--- Example of: flatMap ---
80
85
90
ryan.score.onNext(95)
--- Example of: flatMap ---
80
85
90
95
charlotte.score.onNext(100)
--- Example of: flatMap ---
80
85
90
95
100

switchMap operator

According to the documentation, switchMap: “Applies the given io.reactivex.functions.Function to each item emitted by a reactive source, where that function returns a reactive source, and emits the items emitted by the most recently projected of these reactive sources.”

exampleOf("switchMap") {

  val ryan = Student(BehaviorSubject.createDefault(80))
  val charlotte = Student(BehaviorSubject.createDefault(90))

  val student = PublishSubject.create<Student>()

  student
      .switchMap { it.score }
      .subscribe { println(it) }

  student.onNext(ryan)

  ryan.score.onNext(85)

  student.onNext(charlotte)

  ryan.score.onNext(95)

  charlotte.score.onNext(100)
}
--- Example of: switchMap---
80
85
90
100

Observing events

There may be times when you want to convert an Observable into an Observable of its events. One typical scenario where this is useful is when you do not have control over an Observable that has Observable properties, and you want to handle error events to avoid terminating outer sequences. Don’t worry, it will get clearer in a couple of moments, just hang in there.

materialize operator

The materialize operator can do exactly that. It takes a normal Observable and turns it into an Observable that emits Notification objects that wrap the event type - whether it’s onNext, onComplete or onError.

exampleOf("materialize/dematerialize") {

  val subscriptions = CompositeDisposable()

  val ryan = Student(BehaviorSubject.createDefault(80))
  val charlotte = Student(BehaviorSubject.createDefault(90))

  val student = BehaviorSubject.create<Student>(ryan)
}

// 1
val studentScore = student
    .switchMap { it.score }
// 2
subscriptions.add(studentScore
    .subscribe {
      println(it)
    })
// 3
ryan.score.onNext(85)

ryan.score.onError(RuntimeException("Error!"))

ryan.score.onNext(90)
// 4
student.onNext(charlotte)
--- Example of: materialize and dematerialize ---
80
85
io.reactivex.exceptions.OnErrorNotImplementedException: Error!

val studentScore = student
    .switchMap { it.score.materialize() }
--- Example of: materialize/dematerialize ---
OnNextNotification[80]
OnNextNotification[85]
OnErrorNotification[java.lang.RuntimeException: Error!]
OnNextNotification[90]

dematerialize operator

That’s where dematerialize comes in. It will convert a materialized Observable back into its original form.

studentScore
    // 1
    .filter {
      if (it.error != null) {
        println(it.error)
        false
      } else {
        true
      }
    }
    // 2
    .dematerialize { it }
    .subscribe {
      println(it)
    }
    .addTo(subscriptions)
--- Example of: materialize/dematerialize ---
80
85
java.lang.RuntimeException: Error!
90

Challenge

Challenge: Sending alpha-numeric characters

In Chapter 5’s challenge, you created a phone number lookup using filtering operators. You added the code necessary to look up a contact based on a 10-digit number entered by the user.

val convert: (String) -> Int = { value ->
  val number = try {
    value.toInt()
  } catch (e: NumberFormatException) {
    val keyMap = mapOf(
        "abc" to 2, "def" to 3, "ghi" to 4, "jkl" to 5,
        "mno" to 6, "pqrs" to 7, "tuv" to 8, "wxyz" to 9)

    keyMap.filter { it.key.contains(value.toLowerCase()) }
        .map { it.value }.first()
  }

  if (number < 10) {
    number
  } else {
    // RxJava 2 does not allow null in stream, so return
    // sentinel value
    sentinel
  }
}
val format: (List<Int>) -> String = { inputs ->
  val phone = inputs.map { it.toString() }.toMutableList()
  phone.add(3, "-")
  phone.add(7, "-")
  phone.joinToString("")
}

val dial: (String) -> String = { phone ->
  val contact = contacts[phone]
  if (contact != null) {
    "Dialing $contact ($phone)..."
  } else {
    "Contact not found"
  }
}

Key points

  • Transforming operators let you transform observable items from their original type to another type or value.
  • You can use toList to turn a normal observable into an observable that emits a single list.
  • The map operator will transform individual elements in an observable to some other value or type.
  • You can use flatMap to flatten an observable stream of observables into one stream of items.
  • Similarly, switchMap will also flatten a stream of observables, but this time only listening to the observable in the source that has most recently emitted.
  • You use materialize to make observables emit notifications of events rather than the events themselves, and dematerialize to transform from the notification type back to the original type.

Where to go from here?

Just like for the earlier chapters on filtering operators, you’ll want to gain experience using transforming operators in a real Android app project. That’s up next!

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.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now