tonga
tonga

Reputation: 335

How to call this API in Kotlin?

I'm trying to call this API: https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?number=2

As you can see, it returns a Json with random facts about dogs, my problem is when I put my baseUrl with retrofit:

private fun getRetrofit(): Retrofit{
        return Retrofit.Builder()
            .baseUrl("https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?number=")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

If I put the url like that, it doesn't work, because it says that must end with an "/" But if I add it, it doesn't work.

Also, I tried to put just like:

.baseUrl("https://dog-facts-api.herokuapp.com/api/v1/resources/")

And making the call like this:

val call = getRetrofit().create(APIService::class.java).getFactsByNumber("dogs?number=$number")

But again, it didn't work, it throws an error:

E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1 Process: com.eltonga.dogfacts, PID: 11927 com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $

My Interface:

interface APIService {
    @GET
    suspend fun getFactsByNumber(@Url url: String): Response<FactsResponse>
}

My data class:

data class FactsResponse(var fact: List<String>)

The function where I call the API:

private fun searchByNumber(number:Int){
        CoroutineScope(Dispatchers.IO).launch {
            val call = getRetrofit().create(APIService::class.java).getFactsByNumber("dogs?number=$number")
            val the_facts = call.body()

            runOnUiThread {
                if(call.isSuccessful){
                    val factsData = the_facts?.fact ?: emptyList()
                    facts.clear()
                    facts.addAll(factsData)
                    adapter.notifyDataSetChanged()
                }else{
                    Toast.makeText(binding.btnGo.context, "Error", Toast.LENGTH_SHORT).show()
                }
            }

        }
    }

What can I do? I'm pretty new working with API

Upvotes: 1

Views: 4720

Answers (1)

Madhu Bhat
Madhu Bhat

Reputation: 15183

Expected BEGIN_OBJECT but was BEGIN_ARRAY

The above error indicates that the API response has an array/list at the root and you are trying to deserialize the response to an object that is not an array/list.

interface APIService {
    @GET
    suspend fun getFactsByNumber(@Url url: String): Response<FactsResponse>
}

data class FactsResponse(var fact: List<String>)

This would work if the response is in the below format

{
  "fact": [
    "Greyhounds can reach a speed of up to 45 miles per hour.",
    "The average dog can run about 19 mph. Greyhounds are the fastest dogs on Earth and can run at speeds of 45 mph."
  ]
}

But since the response is in the below format

[
  {
    "fact": "Greyhounds can reach a speed of up to 45 miles per hour."
  },
  {
    "fact": "The average dog can run about 19 mph. Greyhounds are the fastest dogs on Earth and can run at speeds of 45 mph."
  }
]

You need to have the return type wrapped with a List and the class should have only a fact String field. So the function and data class should be defined as

interface APIService {
    @GET
    suspend fun getFactsByNumber(@Url url: String): Response<List<FactsResponse>>
}

data class FactsResponse(val fact: String)

Also, a better way of defining the interface function to avoid passing it "dogs?number=$number" everytime is

interface APIService {
    @GET("dogs")
    suspend fun getFactsByNumber(@Query("number") number: String): Response<List<FactsResponse>>
}

And calling the function as

val apiService = getRetrofit().create(APIService::class.java)

val call = apiService.getFactsByNumber(number) // where number is already defined and initialized with a value

Upvotes: 2

Related Questions