How to POST and GET that data from a web API. (Retrofit/Kotlin/Android)

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)
    }
}

backend of the github project

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

  1. 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'
  1. 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)
    
  2. 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:

  1. 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)

  2. 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

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published