Reputation: 489
I have the following code which makes an API call to get Addresses from Postcode.
fun getAddressFromPostCode(postCode: String): List<Address>{
val trimmedPostCode = postCode.replace("\\s".toRegex(),"").trim()
val dataBody = JSONObject("""{"postcode":"$trimmedPostCode"}""").toString()
val hmac = HMAC()
val hmacResult = hmac.sign(RequestConstants.CSSecretKey, dataBody)
val body = JSONObject("""{"data":$dataBody, "data_signature":"$hmacResult"}""")
val url = RequestConstants.URL
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api:GetAddressAPIService = retrofit.create(GetAddressAPIService ::class.java)
var myList = emptyList<Address>()
val myCall: Call<GetAddressResponse> = api.getAddress(body)
myCall.enqueue(object : Callback<GetAddressResponse> {
override fun onFailure(call: Call<GetAddressResponse>?, t: Throwable?) {
Log.d("RegistrationInteractor", "Something went wrong", t)
Log.d("RegistrationInteractor", call.toString())
}
override fun onResponse(call: Call<GetAddressResponse>?, response: Response<GetAddressResponse>?) {
// Success response
myList = response!!.body()!!.addresses
}
})
return myList
}
And here's where I make the call:
interface GetAddressAPIService {
@Headers("Accept: application/json; Content-Type: application/json")
@POST("postcode_search.php")
fun getAddress(@Body dataBody: JSONObject): Call<GetAddressResponse>
} The GetAddressResponse looks like this and seems to be correct: data class GetAddressResponse( val success: Int, val addresses: List )
The databody is {"data":{"postcode":"M130EN"},"data_signature":"long data signature"} and when I run this in Postman I get a list of addresses, but when I run it in the app I get a 200 OK response but no addresses. Any ideas why?
Upvotes: 1
Views: 3819
Reputation: 10487
Your code is returning but enqueue
is async so that is not guaranteed to happen before the return. If you read the enqueue
there is a callback there, which means it is gonna call
back
after is ready, since it is an HTTP request then that takes some time and it is finished after the return.
You have 2 alternatives, add the Callback<GetAddressResponse>
as an argument or any other callback as an argument, or you can make it suspend.
Coroutines are the recommended way of doing things now so this is no longer considered a good practice.
fun getAddressFromPostCode(postCode: String, callback: Callback<GetAddressResponse): List<Address>{
...
myCall.enqueue(callback)
}
The calling class has to implement the callback and pass it as an argument. You can make that more kotlin way by using a lambda:
fun getAddressFromPostCode(postCode: String, callback: (items: List<Address> -> Unit))
...
override fun onResponse(call: Call<GetAddressResponse>?, response: Response<GetAddressResponse>?) {
callback(response...)
}
}
So that makes the calling class use it this way
yourClass.getAddressFromPostCode(postalCode) { -> items
//do your thing with items
}
You can transform it to linear code by using suspend functions:
//make it return optional to know if it was failure
suspend fun getAddressFromPostCode(postCode: String): List<Address>? {
...
return try {
myCall.invoke().result?.body()
} catch (e: Exception) {
null
}
}
And then the calling class has to use it like this:
lifeCycleScope.launch {
val addresses = yourClass.getAddressFromPostCode(postalCode)
//do your thing on the UI with the addresses maybe pass it to an adapter
}
Upvotes: 2
Reputation: 548
The problem you're facing here is asynchronously data fetching, while returning immediately from function.
In code you provided, myCall.enqueue
call is asynchronous and once it's done, it invokes onFailure
or onResponse
, depending if the request is failed or not. On the last line of getAddressFromPostCode
function, you return myList
value, which is still empty in that case, because onResponse
has not been called yet.
I can suggest to get familiar with coroutines, which would make this code easier to reason about, while handling asynchronous work. Or you could simply provide a callback function to getAddressFromPostCode
to be called once the request is done or failed.
Upvotes: 0