Search code examples
androidrestkotlinretrofit2kotlin-coroutines

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


Solution

  • 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