Step #4: Retrieving the Items

Now, we can add some code that will download the JSON and convert it into a list of ToDoServerItem objects.

Right-click over the com.commonsware.todo.repo package in the java/ directory and choose “New” > “Kotlin File/Class” from the context menu. For the name, fill in ToDoRemoteDataSource, and choose “Class” for the kind. Press Enter or Return to create the class. Then, replace the class contents with:

package com.commonsware.todo.repo

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request

class ToDoRemoteDataSource(private val ok: OkHttpClient) {
  private val moshi = Moshi.Builder().add(MoshiInstantAdapter()).build()
  private val adapter: JsonAdapter<List<ToDoServerItem>> = moshi.adapter(
    Types.newParameterizedType(
      List::class.java,
      ToDoServerItem::class.java
    )
  )

  suspend fun load(url: String) = withContext(Dispatchers.IO) {
    val response = ok.newCall(Request.Builder().url(url).build()).execute()

    if (response.isSuccessful) {
      response.body?.let { adapter.fromJson(it.source()) }
        ?: throw IOException("No response body: $response")
    } else {
      throw IOException("Unexpected HTTP response code: ${response.code}")
    }
  }
}

This class has a load() function that orchestrates our work for downloading and parsing the JSON. This will involve network I/O, so load() is defined as a suspend function and it uses withContext(Dispatchers.IO) to use the coroutine system to run this code on a background thread.

First, we need to download the JSON, and for that, we use OkHttp. Our constructor gets an OkHttpClient, which is our entry point for using OkHttp. load() gets the URL of the JSON as a parameter. We then:

This will make the connection to the server and try to download the JSON. We get a Response object back which (hopefully) contains our JSON along with other bits of information from the Web service, such as an HTTP response code (e.g., 200 for an “OK” response). We check to see if the Response is successful by checking the isSuccessful property. If it was not successful, we throw an exception indicating the nature of the problem (e.g., our URL is wrong and we got a 404 response from the server).

We then check to see if the response has a body (body()) — this represents the JSON itself. If, for some reason, we do not have a body, we throw an exception to indicate that fact.

Finally, if we have a successful response and it has a body, we need to try to parse the JSON. For that, we use Moshi. Our ToDoRemoteDataSource has a moshi property which is a Moshi object, created using Moshi.Builder. In our case, we teach Moshi how to handle Calendar properties by adding our MoshiInstantAdapter() to the Moshi instance.

By and large, Moshi is a series of adapter classes. Some we write ourselves, such as MoshiInstantAdapter(). Some are code-generated for us at compile time, such as the adapter for ToDoServerItem that we requested via the @JsonClass(generateAdapter = true) annotation that we placed on the ToDoServerItem class. And some are code-generated for us at runtime, such as the adapter property that we have in ToDoRemoteDataSource. That builds a JsonAdapter that knows how to parse JSON into a List of ToDoServerItem objects. In load(), we pass our JSON (source() called on the response.body() object) to this JsonAdapter, and it will return our List of ToDoRemoteDataSource objects… or will throw an exception is there is some parsing problem.

Kotlin does not use Java-style checked exceptions, but it is obvious that we have multiple possible exceptions coming from load(). Given that we are attempting to download data from the Internet, there are lots of ways that this can go wrong, all of which will lead to exceptions.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.