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.