I am trying to get CatAPI to work. My code is compiling and running, and the Dog API (which I successfully implemented) works just fine. However, I get this error whenever I use the button for the CatAPI:
I/catError: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
I suppose it has something to do with the way I tried to read the JSON. However, I have no idea what to do here.
Here is my code:
CatService.kt:
interface CatService{
#GET("images/search")
fun randomCat(): Call<Cat>
}
Cat.kt
class Cat (
var id: String,
var url: String,
var breeds: List<Any>,
var width: Int,
var height: Int
)
MainActivity.kt:
private fun catFunction(){
val retrofitCat = Retrofit.Builder()
.baseUrl("https://api.thecatapi.com/v1/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val catService: CatService = retrofitCat.create(CatService::class.java)
val callCat = catService.randomCat().enqueue(object:Callback<Cat>{
override fun onResponse(call: Call<Cat>, response: Response<Cat>) {
val randomCat = response.body()!!
Log.i("catMessage", randomCat.url)
Picasso.get()
.load(randomCat.url)
.into(imgView)
}
override fun onFailure(call: Call<Cat>, t: Throwable) {
Log.i("catError", t.toString())
}})
}
Thank you!
Futhering my comments you would want to return a list of cats i.e. List<Cat> as its a JSONArray.
CatService.kt:
interface CatService{
#GET("images/search")
fun randomCat(): Call<List<Cat>>
}
your actual enqueue:
val callCat = catService.randomCat().enqueue(object : Callback<List<Cat>> {
override fun onResponse(call: Call<List<Cat>>, response: Response<List<Cat>>) {
val randomCat = response.body()!!
Log.i("catMessage", randomCat[0].url)
Picasso.get()
.load(randomCat[0].url) // get the first result as the API states
.into(imgView)
}
override fun onFailure(call: Call<List<Cat>>, t: Throwable) {
Log.i("catError", t.toString())
}
})
Notice all the changes from Call<Cat> to Call<List<Cat>> and Response<Cat> to Response<List<Cat>>.
Related
I've got the root of the problem of this implmenetation:
How to delete with Body using retrofit with usecase?
In brief:
With this usecase method like underneath I return Unit.
My logs didn't show it directly, but in debugger I have seen that the problem is because of
Service methods cannot return void.
for method NotificationApi.deleteToken
if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {
How to change this method deleteToken to work with delete and not return void/unit?
How to provide it with usecase properly?
Thanks.
This is my UseCase
class DeleteTokenSecondUseCase #Inject constructor(private val api: NotificationApi) :
BaseApiRequestUseCase<DeleteTokenSecondUseCase.Params, Unit>() {
override fun create(params: Params): Flow<Unit> = flow {
api.deleteToken(NotificationApi.TokenChangedBody(params.token))
}
data class Params(val token: String)
}
this is my delete
#HTTP(method = "DELETE", path = "account/firebase", hasBody = true)
fun deleteToken(#Body body: TokenChangedBody)
data class TokenChangedBody(val token: String)
fun method in vm
fun notifyNotificationTokenChanged(token: String) {
val params = DeleteTokenSecondUseCase.Params(token)
deleteTokenSecondUseCase.buildWithState(params)
.withSuccess { Log.d("build", "WORKS $params") }
.withError { Log.d("build", "NOT WORKS $params") }
.launchIn(viewModelScope)
}
EDIT:
I have implmeneted it with Call event but still it goes onFailure, guys why?
#HTTP(method = "DELETE", path = "account/firebase", hasBody = true)
fun deleteToken(#Body body: TokenChangedBody) : Call<ResponseBody>
fun notifyNotificationTokenChanged(token: String) {
val params = NotificationApi.TokenChangedBody(token)
val deleteRequest: Call<ResponseBody> = notificationApi.deleteToken(params)
deleteRequest.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(call: Call<ResponseBody?>?, response: Response<ResponseBody?>?) {
Log.d("apitoken", "WORKS")
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) {
Log.d("apitoken", "DOESNT WORK")
}
})
}
EDIT2:
WORKS!
SOLUTION
fun notifyNotificationTokenChanged(token: String) {
val params = NotificationApi.TokenChangedBody(token)
val deleteRequest: Call<Void> = notificationApi.deleteToken(params)
deleteRequest.enqueue(object : Callback<Void?> {
override fun onResponse(call: Call<Void?>?, response: Response<Void?>?) {
}
override fun onFailure(call: Call<Void?>?, t: Throwable?) {
}
})
}
#HTTP(method = "DELETE", path = "account/firebase", hasBody = true)
fun deleteToken(#Body body: TokenChangedBody) : Call<Void>
Someone told in Kotlin use :Completable, but haven't test it.
stackoverflow.com/questions/35429481/retrofit-2-void-return
I try to post data and get the response with POST using retrofit. In postman it works fine but in code it error.
this is the last thing I tried
service
#FormUrlEncoded
#POST("program/get/list-user-program")
fun getProgram(
#Field("comunity") comunity:String,
#Field("programGroupUrl") programGroupUrl:Int
): Call<ListProgramResponse>
activity
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(URL_BASE)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService: Service = retrofit.create(Service::class.java)
val call: Call<ListProgramResponse> = apiService.getProgram("abc", 0)
call.enqueue(object : Callback<ListProgramResponse> {
override fun onResponse(call: Call<ListProgramResponse>, response: Response<ListProgramResponse>) {
Log.e("check", response.body()?.toString())
override fun onFailure(call: Call<ListProgramResponse>, t: Throwable) {
Toast.makeText(applicationContext, "Failure", Toast.LENGTH_SHORT).show()
t.printStackTrace()
}
})
response
data class ProgramBersedekahResponse(
#SerializedName("banner") val banner: String?,
#SerializedName("domainlembaga") val domainlembaga: String?,
#SerializedName("domainprogram") val domainprogram: String?,
#SerializedName("donate") val donatur: Int?
)
but it says java.lang.NumberFormatException: empty String and failure. did anyone know why? please help
I want to show response POST from retrofit. I don't really know how because my response are array but with POST.
this is how my response looks like
{
"message":[
"00",
"Get Success"
],
"result":{
"listProgram":[
{
"banner":"",
"area":"",
"domainlembaga":"",
"domainprogram":"",
"donate":0
{
.
.
.
]
service
interface Service {
#POST("program/list")
fun getProgram(#Body body: Pair<String, Int>, pair: Pair<String, Int>): Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>
data
data class BaseBersedekahResponse<T>(
#SerializedName("message") val message: String?,
#SerializedName("result") val result: T?
)
data class ListProgramBersedekahResponse(
#SerializedName("listProgram") val listProgram: List<ProgramBersedekahResponse>?
)
data class ProgramBersedekahResponse(
#SerializedName("banner") val banner: String?,
#SerializedName("domainlembaga") val domainlembaga: String?,
#SerializedName("domainprogram") val domainprogram: String?,
#SerializedName("donate") val donatur: Int?
)
activity
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(APIUrl.BASEURL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService: Service = retrofit.create(Service::class.java)
val call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>> = apiService.getProgram(
"limit" to 1,
"group" to 4)
call.enqueue(object : Callback<BaseBersedekahResponse<ListProgramBersedekahResponse>> {
override fun onResponse(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>, response: Response<BaseBersedekahResponse<ListProgramBersedekahResponse>>) {
val getProgram: List<ProgramBersedekahResponse?>? = response.body().result.listProgram
Toast.makeText(applicationContext, "success", Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>, t: Throwable) {
Toast.makeText(applicationContext, "Failure", Toast.LENGTH_SHORT).show()
}
})
I actually planning it to show in RecyclerView, but I still don't know how to show it the response from POST. please help.
Your signatures should match each other. You should make your signatures same as the signature in your interface.
For your request body:
data class BersedekahRequest(
#SerializedName("limint") val limint: Int,
#SerializedName("group") val group: Int
)
then your interface:
#POST("program/list")
fun getProgram(#Body body: BersedekahRequest): Call<BaseBersedekahResponse<ListProgramBersedekahResponse>>
your rest service call:
val requestBody = BersedekahRequest(10,20)
val call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>> = apiService.getProgram(requestBody)
call.enqueue(object : Callback<BaseBersedekahResponse<ListProgramBersedekahResponse>> {
override fun onResponse(call: Call<BaseBersedekahResponse<ListProgramBersedekahResponse>, response: Response<BaseBersedekahResponse<ListProgramBersedekahResponse>>) {
val GetApps2: List<ProgramBersedekahResponse?>? = response.body().result.listProgram
Toast.makeText(applicationContext, "success", Toast.LENGTH_SHORT).show()
}
override fun onFailure(call: Call<ListProgramBersedekahResponse>, t: Throwable) {
Toast.makeText(applicationContext, "Failure", Toast.LENGTH_SHORT).show()
}
})
And I think one more problem you have.
fun getProgram(#Body body: ProgramBersedekahResponse?) this #Body is the body you will send via POST request, not your response body. If you do not send anything inside post body, make it empty, or give the appropriate request body since it seems like it is your response object right now.
I found some Api about Pokemon(https://pokeapi.co). And I try get data from this Api and it's work.
The data look like this
But I try to use name of data from Api to get some image with this path "api/v2/pokemon/{name}"
The problem is how can get name out of onResponse or do the other way to get name and image of Pokemon
MainActivity
val retrofit = Retrofit.Builder()
.baseUrl("https://pokeapi.co/")
.addConverterFactory(GsonConverterFactory.create())
.client(HTTPLogger.getLogger())
.build()
val jsonPlaceholderApi = retrofit.create(pokemonService::class.java)
val myCall: Call<PokemonInGen> = jsonPlaceholderApi.getGen(1)
myCall.enqueue(object : Callback<PokemonInGen> {
override fun onResponse(
call: Call<PokemonInGen>,
response: Response<PokemonInGen>
) {
val DataResponse: PokemonInGen = response.body()!!
Timber.i("on do Respon %s", DataResponse)
}
override fun onFailure(call: Call<PokemonInGen>, t: Throwable) {
Timber.i("on do ERROR")
}
})
My Service
interface pokemonService {
#GET("api/v2/generation/{id}")
fun getGen(
#Path("id") id: Int,
): Call<PokemonInGen>
#GET("api/v2/pokemon/{name}")
fun getArtwork(
#Path("name") name: String,
): Call<PokemonArtwork>
}
My Model Data class
data class PokemonInGen(
val pokemon_species: List<PokemonList>)
data class PokemonList(
val name: String,
val url: String,
)
To fetch Pokemon image you should create additional function in your MainActivity class
fun fetchPokemonArtwork(name: String) {
jsonPlaceholderApi.getArtwork(name).enqueue(object : Callback<PokemonArtwork> {
override fun onResponse(
call: Call<PokemonArtwork>,
response: Response<PokemonArtwork>
) {
// An artwork is successful fetched
val artwork = response.body()!!
}
override fun onFailure(call: Call<PokemonArtwork>, t: Throwable) {
// Handle a failure
}
})
}
You should call this function right after you fetched a Pokemon in generation
val myCall: Call<PokemonInGen> = jsonPlaceholderApi.getGen(1)
myCall.enqueue(object : Callback<PokemonInGen> {
override fun onResponse(
call: Call<PokemonInGen>,
response: Response<PokemonInGen>
) {
val DataResponse: PokemonInGen = response.body()!!
Timber.i("on do Respon %s", DataResponse)
// Extract name
val name = DataResponse.pokemon_species.first().name
// Fetch an artwork
fetchPokemonArtwork(name)
}
override fun onFailure(call: Call<PokemonInGen>, t: Throwable) {
Timber.i("on do ERROR")
}
})
P.S. I proceeded from the assumption that you've implemented PokemonArtwork class. Please let me know if you are facing difficulties in comments below.
P.S.S. It's not recommended to make network calls in Activity or Fragment classes. This guide to app architecture should help you to select correct app structure in your future releases.
You can using #Url to support dynamic link in retrofit. Example below:
interface pokemonService {
#GET
Call<PokemonResponse> getListPokemon(#Url String url);
}
And paste your url to browser to see data format.
when I use retrofit2 with no coroutine, the result is null. but when using that with coroutine, the result is right. I think it's the problem of syncronization. but I found something strange
using mutablelivedata, the result is right.
retrofit2 with coroutine
override suspend fun getRetrofit(id : Int): DetailEntity {
withContext(ioDispatcher){
val request = taskNetworkSource.searchItem(id)
val response = request.await()
if(response.body !=null){
Log.d("TAG",""+response.toString())
data = response
}
}
return data
}
good result
D/TAG: DetailEntity(body=DetatilItem(oily_score=6, full_size_image=url, price=54840, sensitive_score=76, description=description, id=5, dry_score=79, title=title), statusCode=200)
retrofit2 with no coroutine
override suspend fun getRetrofit(id : Int): DetailEntity {
taskNetworkSource.searchItem(id).enqueue(object: Callback<DetailEntity> {
override fun onFailure(call: Call<DetailEntity>, t: Throwable) {
}
override fun onResponse(call: Call<DetailEntity>, response: Response<DetailEntity>){
if(response.body()!=null) {
Log.d("TAG",response.toString())
data = response.body()!!
}
}
})
return data
}
bad result
D/TAG: Response{protocol=h2, code=200, message=, url=https://6uqljnm1pb.execute-api.ap-northeast-2.amazonaws.com/prod/products/5}
strange result with mutablelivedata(another project code)
lateinit var dataSet : DetailModel
var data = MutableLiveData<DetailModel>()
fun getDetailRetrofit(id:Int) : MutableLiveData<DetailModel>{
Retrofit2Service.getService().requestIndexItem(id).enqueue(object:
Callback<DetailResponse> {
override fun onFailure(call: Call<DetailResponse>, t: Throwable) {
}
override fun onResponse(call: Call<DetailResponse>, response: Response<DetailResponse>) {
if(response.body()!=null) {
var res = response.body()!!.body
dataSet = DetailModel( res.get(0).discount_cost,
res.get(0).cost,
res.get(0).seller,
res.get(0).description+"\n\n\n",
res.get(0).discount_rate,
res.get(0).id,
res.get(0).thumbnail_720,
res.get(0).thumbnail_list_320,
res.get(0).title
)
data.value = dataSet
}
}
})
return data
}
and this another project code result is right. comparing this code to retrofit2 with no coroutine code, the difference is only mutablelivedata or not. do I have to use asyncronouse library or livedata?
added
data class DetailEntity(val body: DetatilItem,
val statusCode: Int = 0)
data class DetatilItem(val oily_score: Int = 0,
val full_size_image: String = "",
val price: String = "",
val sensitive_score: Int = 0,
val description: String = "",
val id: Int = 0,
val dry_score: Int = 0,
val title: String = "")
retrofit with no coroutine it seem to be no problem.
But, respnose at your code written to log are the completely different object.
with coroutine, response is DetailEntity
with no coroutine, response is Response<DetailEntity>
if you want same log print, try as below
override fun onResponse(call: Call<DetailEntity>, response: Response<DetailEntity>){
if(response.body()!=null) {
Log.d("TAG",response.body()!!.toString())
data = response.body()!!
}
}
Reference
Retrofit - Response<T>