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

18. Retrofit
Written by Alex Sullivan

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Throughout this book, you’ve often used the popular Retrofit library to build your apps. In this chapter, you’ll further explore how exactly Retrofit interfaces with the Rx world and how you can take advantage of all that it offers.

Getting started

For this chapter, you’ll build a JSON-viewing app. The app you’ll build will allow you to add rows to a JSON object, save that object to the JSONBlob (https://jsonblob.com/) storage API and then retrieve that saved JSON string.

While building the app, you’ll explore the different options you have when interacting with Retrofit.

Open the starter project for the chapter and run the app. You’ll see a white screen with an empty JSON object, signified with the {} text. You’ll also see two EditTexts and a FloatingActionButton (FAB) at the bottom of the screen.

That’s where you’ll add the new rows for the JSON object.

Recap of Retrofit

Before you start exploring how Retrofit interacts with RxJava, it’s worth taking a moment to recap what Retrofit is.

// 1
interface GitHubApi {
  // 2
  @GET("repos/ReactiveX/{repo}/events")
  // 3
  fun fetchEvents(@Path("repo") repo: String,
      @Header("If-Modified-Since") lastModified: String)
      // 4
    : Observable<Response<List<AnyDict>>>
}

Including Rx adapters

Open the JsonBinService class and look at the create method in the companion object. At the bottom of the method, you’re declaring an instance of the Retrofit object with the following code:

val retrofit = Retrofit.Builder()
  .baseUrl(JsonBinApi.API)
  .client(client)
  .addConverterFactory(ScalarsConverterFactory.create())
  .addConverterFactory(GsonConverterFactory.create())
  .build()
implementation "com.squareup.retrofit2:adapter-rxjava3:$retrofit_version"
val retrofit = Retrofit.Builder()
  .baseUrl(JsonBinApi.API)
  .client(client)
  .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
  .addConverterFactory(ScalarsConverterFactory.create())
  .addConverterFactory(GsonConverterFactory.create())
  .build()

Creating a JSON object

Now that you’ve got Retrofit properly configured, it’s time to create a JSON object and save it with the JSONBlob API.

private val clicks = PublishSubject.Create<Unit>()
private val keyChanges = BehaviorSubject.create<CharSequence>()
private val valueChanges =
    BehaviorSubject.create<CharSequence>()
val buttonObservable = clicks
  .flatMap {
    Observables.combineLatest(keyChanges, valueChanges)
  }
  .share()

@POST("jsonBlob")
@Headers("Content-Type:application/json")
fun createJson(@Body json: String): Observable<Response<String>>
return service.createJson(json).map {
  it.headers().get("Location")
}
val creationObservable = buttonObservable
  // 1
  .take(1)
  // 2
  .map { "{\"${it.first}\":\"${it.second}\"}" }
  .doOnNext { jsonTextLiveData.postValue(it) }
  // 3
  .flatMap {
    JsonBinApi.createJson(it).subscribeOn(Schedulers.io())
  }
  // 4
  .map { it.substringAfterLast("/") }
  .cache()
creationObservable
  .subscribe()
  .addTo(disposables)

Updating the JSON

After creating and storing a JSON object in the JSONBlob API, it’s time to update that object with new values.

val updateObservable = creationObservable
  .flatMap { buttonObservable }
  .map {
    createNewJsonString(it.first, it.second,
      jsonTextLiveData.value!!)
  }

updateObservable
  .subscribe {
    jsonTextLiveData.postValue(it)
  }
  .addTo(disposables)

val buttonObservable = clicks
  .flatMap {
    Observables.combineLatest(keyChanges, valueChanges)
  }
  .share()
val buttonObservable = clicks
  .flatMap {
    Observables.combineLatest(keyChanges, valueChanges)
      .take(1)
  }
  .share()
@PUT("jsonBlob/{id}")
@Headers("Content-Type:application/json")
fun updateJson(@Path("id") binId: String, @Body json: String): Completable
return service.updateJson(bin, json)
val updateObservable = creationObservable
  // 1
  .flatMap { binId ->
    buttonObservable
      // 2
      .map { createNewJsonString(it.first, it.second,
        jsonTextLiveData.value!!) }
      .map { binId to it }
  }
  // 3
  .flatMapCompletable {
    JsonBinApi.updateJson(it.first, it.second)
      .subscribeOn(Schedulers.io())
  }
updateObservable
  .subscribe()
  .addTo(disposables)

Retrieving JSON

Open the JsonBinService class again and add the following method below the updateJson method you added earlier:

@GET("jsonBlob/{id}")
@Headers("Content-Type:application/json")
fun getJson(@Path("id") binId: String): Single<Response<String>>

Handling errors

This is a good time to pause for a moment and consider how error handling works in Retrofits RxJava integration. No matter what reactive return type you specify, whether its Observable, Single, Completable, or Maybe, if you do not have internet access when you attempt to make a call through Retrofit you will hit the error block of your subscriber. Not particularly surprising but good to point out.

Tying it all together

Now that you’ve got a method in your Retrofit interface to retrieve JSON, you need to update the JsonBinApi class to reference the new method. Replace the body of the getJson method with the following:

return service.getJson(bin)
// 1
.flatMap { pair ->
  // 2
  JsonBinApi.updateJson(pair.first, pair.second)
    // 3
    .andThen(JsonBinApi.getJson(pair.first))
    .toObservable()
    .subscribeOn(Schedulers.io())
}
updateObservable
  .subscribe {
    if (it.isSuccessful) {
      val prettyJson = JSONObject(it.body()!!).toString(4)
      jsonTextLiveData.postValue(prettyJson)
    } else {
      errorLiveData.postValue("Whoops, we got an error!")
    }
  }
  .addTo(disposables)

Key points

  • In order to return any of the reactive types from a Retrofit interface, you have to make sure to include the RxJava3 call adapter library.
  • Once you do include the call adapter library, you can return any of the reactive types you’ve seen in the book. Observable, Flowable, Completable, Single, Maybe — the whole gang’s here!
  • You can use the Observables (or other reactive types) you receive from Retrofit just like any other Observable. Make sure to use the subscribeOn and observeOn operators to do your network operations off the main thread.
  • You can wrap your custom model types in the Response object to get access to HTTP status codes and errors. You can even nest those types inside your reactive types!
  • Make sure to pay extra special attention to how you handle errors when using Retrofit. If you use the Response object you’ll see fewer exceptions in your subscribe error handling code.

Where to go from here?

Retrofit is a great example of a library that makes use of RxJava in a very pragmatic way. Retrofit is a solid addition to any Android project, and even more so when coupled with 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.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now