Issue
I'm new to web APIs and retrofit. I'm interacting with TheCatApi and having trouble fetching data that I posted. The API has a list of images and a feature to choose several as favorites with POST requests and retrieve a list of favorites.
My main issue probably has to do with the data class (I have 2):
-For the Image GET request
data class CatPhoto (
@Json(name = "id")
val id: String,
@Json(name = "url")
val imgSrcUrl: String,
@Json(name = "breeds")
val breeds: List<Any>,
@Json(name = "width")
val width: Int,
@Json(name = "height")
val height: Int
)
@GET("$API_V/favourites?limit=100")
suspend fun getMyFavorites(
@Query("sub_id") subId: String
): List<CatPhoto>
-To make an image a favorite using a POST request
data class FavouriteImage (
val image_id: String,
val sub_id: String,
)
@POST("$API_V/favourites?limit=100")
suspend fun addFavorite(
@Body favouriteImage: FavouriteImage
)
This is the error after I POST a favourite and try to retrieve a list of posted favourites:
com.squareup.moshi.JsonDataException: Required value 'imgSrcUrl' (JSON name 'url') missing at $[.1]
It looks like it's expecting imgSrcUrl
attribute on the FavouriteImage
data class, which makes me think I shouldn't even have the FavouriteImage
class. But then how do I make the post request that requires image_id
and sub_id
in the body?
Here's how I set up the database in the API service file:
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
object CatsApi {
val catsApiService : CatsApiService by lazy {
retrofit.create(CatsApiService::class.java)
}
}
Solution
Edit: As @extremeoats wrote in comments, the api doesn't support passing urls an image indentifier and you have to save its id also for the operations like making favorite etc.
Old answer
Could you please add some code of how a request is made? It's a local error of Moshi trying to parse the response and not seing the required field (maybe got an error from server - the data structure of an error would be different from a normal response)
I've built a sample app to test this and get a proper response when marking the image as a fav. You are right to use the structure with an image_id and sub_id. Here are some code parts if it helps
- Set up the Retrofit (interceptor for debug only, so you can see what exactly you sent and got back)
private val interceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val retrofit = Retrofit.Builder()
.baseUrl(ServerApi.BASE_URL)
.client(
OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
)
.addConverterFactory(MoshiConverterFactory.create())
.build()
private val api = retrofit.create(ServerApi::class.java)
1.1. A dependency for the logging interceptor and OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
Api interface
interface ServerApi { companion object { const val API_KEY: String = **PASTE YOUR API KEY HERE** const val AUTH_HEADER = "x-api-key" const val BASE_URL = "https://api.thecatapi.com/v1/" } @GET("images/search") fun getImages(@Header(AUTH_HEADER) authHeader: String = API_KEY, @Query("limit") limit: Int = 5): Call<List<ImagesItem>> @POST("favourites") fun postFavourite(@Header(AUTH_HEADER) authHeader: String = API_KEY, @Body payload: PostFavouritePayload): Call<String> } private var listMyData = Types.newParameterizedType(List::class.java, ImagesItem::class.java) private val adapter: JsonAdapter<List<ImagesItem>> = Moshi.Builder().build().adapter(listMyData)
Use the api
api.getImages().enqueue(object : Callback<List<ImagesItem>> { override fun onResponse(call: Call<List<ImagesItem>>, response: Response<List<ImagesItem>>) { val images = response.body() ?: return api.postFavourite(payload = PostFavouritePayload(images[0].id, **PASTE ANY STRING HERE AS USER ID**)) .enqueue(object : Callback<String> { override fun onResponse(call: Call<String>, response: Response<String>) = Log.d("TestCatApi", "SUCCESS posting a fav: ${response.body()}") override fun onFailure(call: Call<String>, t: Throwable) = t.printStackTrace() }) } override fun onFailure(call: Call<List<ImagesItem>>, t: Throwable) = t.printStackTrace() })
Side notes:
For such example APIs I find very useful a plugin "Json to Kotlin class". Alt+K, then you can paste the String response from a Server or in example, and you have a decent starting point for the data classes (not affiliated with them)
Just in case: you can pass a baseUrl to the Retrofit builder like that
Retrofit.Builder() .baseUrl(ServerApi.BASE_URL)...
Then you put in @Get, @Post etc. only the part after the "/" of a base url: "images/search"
Answered By - George
Answer Checked By - David Goodson (JavaFixing Volunteer)