We developers are often in a rush to create our network/api models which results in many on-device crashes and time wasted recompiling our apps. I’ve found that building and testing your model objects locally before deployment saves you time and effort. When I connect to a REST endpoint this is how I do it.

We’ll be using Kotlin data classes with Moshi to create value objects that are tested, have generated type adapters, and correctly handle api responses from a particular endpoint. We’ll also be using Okio to load our data from a test.

Collecting sample json

Let’s start with a consumer complaint database for our example, feel free to use your own. First step is to fire up a browser to inspect the initial json from the link above. It is a good idea to format our sample json by pasting it into your favorite json editor/formatter. Additionally you will want to copy the formatted json into your test resources folder and save it.

I’ve found doing this over the years to be invaluable. The formatted json acts a reference for yourself and future developers. We can test against it and not rely on any external systems. If the backend goes down, our tests will still pass. If anything does happen to change on the server we have a great reference in our tests to help us understand those changes.

Modeling your data

Let’s get started on the data model! We’re going to be using Moshi’s Kotlin code gen package to create a model from Kotlin data classes. Why Moshi? It provides type adapters for us as you would typically have to use a library with gson like Immutables.org or Auto Value. Also, Moshi respect’s the nullability rules of Kotlin and will disallow a null value in a non null field.

Kotlin data classes are also immutable. As mentioned, this makes our lives easier in a lot of ways. By not having to worry about data mutations we reduce complexity around working with data, particularly in a multi threaded environment like Android. If we did want to have the equivalence of a mutation, we can simply use the builtin copy method that we get for free from using Kotlin data classes.

We need to add in our dependencies like:

implementation "com.squareup.moshi:moshi:$moshi_version"

kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version")

Looking at the json data that we got from the url above, we can see that it is made up of an array of complaints with usual scalar values like id, description, createdAt and some json objects like owner and metadata.

@JsonClass(generateAdapter = true)

data class Complaint (

val id: String,

val description: String,

val createdAt: Long,

val owner: Owner

) @JsonClass(generateAdapter = true)

data class Owner (

val id: String,

val displayName: String,

val flags: List<String>

)

Notice the @JsonClass(generateAdapter = true) . That’s how the magic happens — Moshi picks up this annotation and generates all of your type adapters for you. Zac Sweers wrote an excellent article that goes into the code generation internals and advanced usage. I encourage you to check it out.

Loading json for a unit test

Next, let’s add in Okio as a dependency as we’ll need it to load the sample json file from earlier. Why are we doing this again? We want to load the json response to test against and we don’t want to have to hit the server each time.

implementation "com.squareup.okio:okio:$okio_verison"

I’ve had a hard time consistently accessing the file system during tests and have found that the best way is to do something like this:

private fun inputStream(path: String): InputStream {

return ComplaintsTest::class.java!!.getResourceAsStream(path)

} @Test

fun loadData () {

val myStream = inputStream("/gov_complaints.json")

Assert.assertNotNull(myStream)

}

You can see in the above example that we are a getting a runtime reference to the Kotlin test class and using that load the data file as a stream: ComplaintsTest::class.java!!.getResourceAsStream(path)

Now that we can access our data on disk, let’s use Moshi to get it into the form we want. But first, how do we handle that stream? We can see Moshi’s fromJson() method which takes either a JsonReader , BufferedSoruce , or a String . Okio makes transforming a stream a simple one liner like this:

Okio.buffer(Okio.source(inputStream("/gov_complaints.json")))

That’s it! we’re good to go! You now have a buffered source that Moshi can read from. If you’ve never had to deal with a raw list in Moshi, you do it with a ParameterizedType like this:

val listType = Types.newParameterizedType(List::class.java, Complaint::class.java)

And create the Moshi adapter like so:

moshi.adapter<List<Complaint>>(listType)

Putting this altogether we get the following:

Validating your data model

Running our test fails immediately with the following error:

JsonDataException: Required property ‘flags’ missing at $[0].owner

The flags value in one of our json samples is either missing or null which breaks Kotlin’s non nullable type that we declared in our data model. We should probably make this optional. We’ll go ahead and add a “?” on the flags value. We finally end up with:

Running our test over and over again will allow us to validate and fix any missing types. While sometimes you have a json schema, in our case this is a great way to prevent crashes at runtime. When we connect to the endpoint or pass that work to another developer, a good bit of the leg work will already be done and time spent on figuring this out will be kept to a minimum. Any issues or inconsistent data can be caught early.

Summary

Regardless of how you are getting the data or what libraries you are using, this pattern of steps will be applicable to you. Over many years of working on a variety of applications large and small, I have found that this has saved me time and effort again and again. The next time you have to model some data, follow these steps. You’ll impress your coworkers and bring some sanity back.

To summarize: