I received a call from client this morning. They accidentally put the UAT version Android APK in production. This caused multiple devices stop functioning with the not updated server.
Even though it is client's fault, the application should not crash. I decided to investigate the cause.
import com.google.gson.annotations.SerializedName
data class Response(@SerializedName("param1") val param1: String,
@SerializedName("param2") val param2: String,
@SerializedName("param3") val param3: String) {
val param3Int
get() = param3.toIntOrNull()
}
param3
is newly added to the upcoming release. So the server in production’s response does not contain param3
and accessing param3Int
caused a NullPointerException.
However, I am using Retrofit2 to handle the REST request which has error handler to handle the error during retrieving JSON. It turns out it is not called because GSON decides to set the field to null
instead of throwing exception for a missing field.
To solve this problem, we have two solutions:
- Write an Annotation and JsonDeserializer to check for missing field.
- Make all the fields in the data class nullable.
The first one introduces a lot of unnecessary code to code base. The second default the purpose of using Kotlin to write null safe code instead of checking null everywhere.
Then, I find out Moshi.
What is Moshi?
Moshi is a JSON serialization library written by square who creates retrofit and okhttp.
Installation
dependencies {
implementation "com.squareup.retrofit2:converter-moshi:2.3.0"
implementation "com.squareup.moshi:moshi:1.6.0"
implementation "com.squareup.moshi:moshi-kotlin:1.6.0"
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.6.0"
}
Refactor
init {
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Response(@Json(name = "param1") val param1: String,
@Json(name = "param2") val param2: String,
@Json(name = "param3") val param3: String) {
val param3Int
get() = param3.toIntOrNull()
}
We replace GsonConverterFactory
with MoshiConverterFactory
, @SerializedName
with @Json
and add @JsonClass
. Then, it is done.
That’s it?
Yes, that’s it. Now Moshi can parse JSON based on Kotlin declaration to determine a field can be nullable or not.
While GSON is a more well-known library, the releases seems to be stale. Only minor changes is released over the years and we see there is no plan to integrate with Kotlin.
If you are writing Android app with Kotlin (You absolute should use Kotlin over Java in Android), you should use Moshi instead of GSON for JSON serialization library.