Reputation: 335
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
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