Home > Net >  Kotlin Android Room handle empty list of objects in Moshi TypeConverter
Kotlin Android Room handle empty list of objects in Moshi TypeConverter

Time:08-30

I inherited an Android project where I had to rebase the rxJava code to a more pure Kotlin centric code base with a MVVM design pattern. Given my inexperience, along with lots of head scratching and reading, I finally finished the code conversion. Sigh!
But, although testing has gone quite smooth, the new @TypeConverters seems more strict and will not allow empty lists of expected objects to store in the Room database.
When the data loads from the server and the item soil_types is empty e.g. soil_types=[], I get the error: Expected receiver of type models.SoilType, but got java.util.ArrayList.
Here is the converter:

@TypeConverter
fun fromJson(json: String?): List<SoilType>? {
    if(json == null) return null
    return convertJsonToListObject(json)
}

@TypeConverter
fun toJson(objectData: List<SoilType>?): String? {
    if (objectData == null) return null
    return convertListObjectToJson(objectData)
}
//...
inline fun <reified T> convertJsonToListObject(json: String): List<T> =
    moshi.adapter<List<T>>(T::class.java).fromJson(json).orEmpty()

inline fun <reified T> convertListObjectToJson(objectData: List<T>): String =
    moshi.adapter<List<T>>(T::class.java).toJson(objectData)

Here is the Room model to be populated:

@Entity(tableName = "soil")
data class Soil(
        @PrimaryKey val id: Long,
        val user_id: Long,
        val name: String,
        val soil_types: List<SoilType>,
        val updated_at: String,
        val created_at: String
)

I know this is not a good design of a data table column and the relationships that could be built.

My question is how can I allow the converter to accept the empty ArrayList, so I can save the emptyList as String resembling '{}'? Or, I do not want to do this, should I allow for null?

Edit:
I tried changing the converter to the following:

@TypeConverter
fun fromJson(json: String?): List<SoilType> {
    if(json == null || json == "{}") return emptyList()
    return convertJsonToListObject(json)
}

@TypeConverter
fun toJson(objectData: List<SoilType>?): String {
    if (objectData.isNullOrEmpty()) return "{}"
    return convertListObjectToJson(objectData)
}

But the error remains.

Edit 2:
It seems what I am experiencing is built into the Moshi json library . Upon further reading in section Moshi handling of null and absent JSON fields; it seems default values must be declared for data class fields to get expected results:

If the JSON response changes and sets a null field in the JSON then the adapter will fail respecting the non null reference of a val property in the data class and throw a clear exception.

In this case a List of objects that is empty.

CodePudding user response:

The problem is actually not in Room but in Moshi. Based on its documentation, you have to do the following in order to parse JSON lists:

inline fun <reified T> convertJsonToListObject(json: String): List<T> =
    moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java)).fromJson(json).orEmpty()

inline fun <reified T> convertListObjectToJson(objectData: List<T>): String =
    moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java)).toJson(objectData)

There is also a Kotlin-specific way of doing this, but in Moshi's latest release (1.13.0), you still need to opt in to this feature:

import com.squareup.moshi.adapter

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> convertJsonToListObject(json: String): List<T> =
    moshi.adapter<List<T>>().fromJson(json).orEmpty()

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> convertListObjectToJson(objectData: List<T>): String =
    moshi.adapter<List<T>>().toJson(objectData)

Arguably, this only complicates your code so it may be worth waiting before using this Kotlin-specific solution.

  • Related