Home Android & Kotlin Books Reactive Programming with Kotlin

8
Transforming Operators in Practice Written by Alex Sullivan & Marin Todorov

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

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

In the previous chapter, you learned about the real workhorses behind reactive programming with RxJava: the map and flatMap dynamic duo. Of course, those aren’t the only two operators you can use to transform Observables, but a program can rarely do without using those two at least few times. The more experience you gain with these two, the better (and shorter) your code will be.

You’ve already gotten to play around with transforming operators in the safety of an Kotlin project, so hopefully you’re ready to take on a real-life project. Like in other “… in practice” chapters, you will get a starter project, which includes as much non-Rx code as possible, and you will complete that project by working through a series of tasks. In the process, you will learn more about map and flatMap, and in which situations you should use them in your code.

Note: In this chapter, you will need to understand the basics of transforming operators in RxJava. If you haven’t worked through Chapter 7, “Transforming Operators,” do that first and then come back to this chapter.

Without further ado, it’s time to get this show started!

Getting started with GitFeed

I wonder what the latest activity is on the RxKotlin repository? In this chapter, you’ll build a project to tell you this exact thing.

The project you are going to work on in this chapter, named GitFeed, displays the activity of a GitHub repository, such as all the latest likes, forks or comments. To get started with GitFeed, open the starter project for this chapter.

In the chapter, you’ll use Retrofit, a networking library, and Gson, a JSON serialization library. Retrofit has a several nifty utilities that allow it to work particularly well with RxJava.

If you’re not familiar with Retrofit, it’s a simple networking library that allows you to declare your API in an interface and instantiate that API using Retrofits magical annotation processor. You’ll see more about it in Chapter 18, “Retrofit”.

Run the app. You’ll see the following blank screen:

Start off by opening the app module build.gradle file and looking at the Retrofit and Gson dependencies:

def retrofit_version = "2.9.0"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.okhttp3:logging-interceptor:4.3.1"

There are four dependencies to note:

  1. The actual Retrofit dependency.
  2. An adapter that Retrofit provides that makes working with RxJava seamless.
  3. A converter that allows you to use Gson.
  4. An interceptor from the OkHttp library (on which Retrofit is built) that allows you to easily log all network output.

Fetching data from the web

Open GithubService.kt. All that’s there now is a companion object create method that builds up an instance of the GitHubApi Retrofit interface. There’s no actual networking code in here—yet.

@GET("repos/ReactiveX/{repo}/events") // 1
fun fetchEvents(@Path("repo") repo: String) // 2
  : Observable<Response<List<AnyDict>>> // 3
val apiResponse = gitHubApi.fetchEvents(repo)

Transforming the response

It’s time to start doing some transformations! And you’ll mix in some filtering operators too.

apiResponse.filter { response ->
  (200..300).contains(response.code())
}
.map { response ->
  response.body()!!
}
.filter { objects ->
  objects.isNotEmpty()
}
.map { objects ->
  objects.mapNotNull { Event.fromAnyDict(it) }
}

Processing the response

Finish this chain up with the following code:

// 1
.subscribeOn(Schedulers.io())
// 2
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
  // 3
  onNext = { events -> processEvents(events) },
  // 4
  onError = { error ->
    println("Events Error ::: ${error.message}") }
)
// 5
.addTo(disposables)

Persisting objects to disk

It’d be great to be able to persist these GitHub actions to app storage, so you can view them without a network connection. Ideally, the app should first load events up from the local database, then show those saved events in the app RecyclerView. In parallel, the app can fetch new events, show them, and finally save them off to be loaded next time the user opens the app.

EventsStore.saveEvents(events)
eventLiveData.value = EventsStore.readEvents()

Adding a last-modified header

GitFeed is looking pretty good, but there’s still a few issues to iron out. One issue is that the app is being very wasteful when it comes to using a user’s network data. Even if the app already has events saved, it requests all of the events every time it makes a network request.

@GET("repos/ReactiveX/{repo}/events")
fun fetchEvents(
  @Path("repo") repo: String,
  @Header("If-Modified-Since") lastModified: String
): Observable<Response<List<AnyDict>>>
val lastModified = EventsStore.readLastModified()

val apiResponse = gitHubApi.fetchEvents(repo, lastModified?.trim() ?: "")

val apiResponse =
  gitHubApi.fetchEvents(repo, lastModified?.trim() ?: "")
    .share()
apiResponse
  .filter { response ->
    (200 until 300).contains(response.code())
  }
.flatMap { response ->
  // 1
  val value = response.headers().get("Last-Modified")
  if (value == null) {
    // 2
    Observable.empty()
  } else {
    // 3
    Observable.just(value)
  }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
  onNext = { EventsStore.saveLastModified(it) },
  onError = { error ->
    println("Last Modified Error ::: ${error.message}") }
)
.addTo(disposables)

Challenge

Challenge: Fetch top repos and spice up the feed

In this challenge, you will go through one more map/flatMap exercise. You will spice up GitFeed a little bit: instead of always fetching the latest activity for a given repo like RxKotlin, you will find the top trending Kotlin repositories and display their combined activity in the app.

apiResponse
  .flatMap { response: TopResponse ->
    if (response.items == null) {
      Observable.empty()
    } else {
      Observable.fromIterable(
        response.items.map { it["full_name"] as String })
    }
  }
@GET("repos/{repo}/events")
fun fetchEvents(@Path("repo", encoded = true) repo: String)
    : Observable<Response<List<AnyDict>>>

@GET("search/repositories?q=language:kotlin&per_page=5")
fun fetchTopKotlinRepos(): Observable<TopResponse>
class TopResponse(val items: List<AnyDict>?)

Key points

  • GitHub has a nice API to play with. It’s a good place to experiment with transforming operators and Rx in general.
  • Retrofit and Gson are a great networking duo for Android. The fact that Retrofit can return Observables and Singles makes it a good choice for learning Rx.
  • Transforming operators can be chained in a flexible way. Experiment without fear! Sometimes, there’s a better way of chaining them to get the result you want.
  • Always handle errors in network requests to prevent crashes. There can be a number of errors that are out of control. Don’t forget to use the onError case to prevent the app from crashing with an exception.
  • You can easily filter out HTTP Status codes with Rx. Success codes are in the 2xx range, others status codes are mostly errors.
  • Network requests in Android must be subscribed to on a background thread and observed on the main thread.
  • map and flatMap let you transform the data in a server response to something that the app understands.

Where to go from here?

You’ve now seen filtering and transforming operators in action in an Android app. There’s one more type of operator that we’ll consider in detail: combining operators. So, back to IntelliJ in the next chapter to begin your look at how to use combining operators in RxJava.

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.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

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 raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.