12

I cant make Kotlin Serializer work with Retrofit. I am using com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.5.0 package along with Retrofit.

data classes

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


@Serializable
data class YelpSearchResult(
    @SerialName("total") val total: Int,
    @SerialName("businesses") val restaurants: List<YelpRestaurant>
)

data class YelpRestaurant(
    val name: String,
    val rating: Double,
    val price: String,
    @SerialName("review_count") val numReviews: Int,
    @SerialName("distance") val distanceInMeters: Double,
    @SerialName("image_url") val imageUrl: String,
    val categories: List<YelpCategory>,
    val location: YelpLocation
) {

    fun displayDistance(): String {
        val milesPerMeter = 0.000621371
        val distanceInMiles = "%.2f".format(distanceInMeters * milesPerMeter)
        return "$distanceInMiles mi"
    }
}

data class YelpCategory(
    val title: String
)

data class YelpLocation(
    @SerialName("address1") val address: String
)

service interface

public interface YelpService {
    @GET("businesses/search")
    fun searchRestaurants(
        @Header("Authorization") authHeader: String,
        @Query("term") searchTerm: String,
        @Query("location") location: String): Call<YelpSearchResult>
}

The Activity

    val contentType = MediaType.get("application/json")
    val retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(Json.asConverterFactory(contentType))
            .build()

    val yelpService = retrofit.create(YelpService::class.java)


    yelpService.searchRestaurants("Bearer ${api_key}",
        "Something", "Some Location").enqueue(
            object: Callback<YelpSearchResult> {
                override fun onFailure(call: Call<YelpSearchResult>, t: Throwable) {
                    Log.d("MainActivity", "err ${t}")

                }
                override fun onResponse(call: Call<YelpSearchResult>, response: Response<YelpSearchResult>) {
                    Log.d("MainActivity", "succ ${response}")
                }
            }
    )

When Running, the thrown exception is,

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xx.xx/com.xx.xx.MainActivity}: java.lang.IllegalArgumentException: Unable to create converter for class com.xx.xx.YelpSearchResult

Unable to create converter for class com.xx.xx.YelpSearchResult for method YelpService.searchRestaurants

Caused by: kotlinx.serialization.SerializationException: Can't locate argument-less serializer for class YelpSearchResult. For generic classes, such as lists, please provide serializer explicitly.

What am I doing wrong? thanks for any directions.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • why you not use `Parcelize`? – GianhTran Mar 19 '20 at 09:39
  • It is weird to use a serializable or parcelable data class to fetch the api result, it is not a good practice because breaks any clean architecture principle. So, my advice it is to create two data class, one to fetch the api result and another to populate your view, so you only needs a mapper: fun map(yourApiResult: DataClassApi): DataClassUI – Manuel Mato Mar 19 '20 at 10:07
  • please check this issue https://github.com/Kotlin/kotlinx.serialization/issues/337 and the solution https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/custom_serializers.md – darwin Mar 24 '20 at 06:23

2 Answers2

15

You forgot to put @Serializable annotation on top of YelpRestaurant

@Serializable
data class YelpRestaurant {
...
}

YellCategory and YelpLocation should also have the annotation at place.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
1

Add Serializable in your data classes

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


@Serializable
data class YelpSearchResult(
    @SerialName("total") val total: Int,
    @SerialName("businesses") val restaurants: List<YelpRestaurant>
)

@Serializable
data class YelpRestaurant(
    val name: String,
    val rating: Double,
    val price: String,
    @SerialName("review_count") val numReviews: Int,
    @SerialName("distance") val distanceInMeters: Double,
    @SerialName("image_url") val imageUrl: String,
    val categories: List<YelpCategory>,
    val location: YelpLocation
) {

    fun displayDistance(): String {
        val milesPerMeter = 0.000621371
        val distanceInMiles = "%.2f".format(distanceInMeters * milesPerMeter)
        return "$distanceInMiles mi"
    }
}

@Serializable
data class YelpCategory(
    val title: String
)

@Serializable
data class YelpLocation(
    @SerialName("address1") val address: String
)
Mike
  • 1,313
  • 12
  • 21