Retrofit2 enqueue onResponse() in Kotlin - android

Function returns null before data.value is set in asynchronous onResponse().
How to make it first fetch data and then return that data?
fun getNews(code: String): LiveData<List<News>>{
val call = service.getNewsByCountry(code, Constant.API_KEY)
var data = MutableLiveData<List<News>>()
call.enqueue(object : Callback<NewsResponse> {
override fun onFailure(call: Call<NewsResponse>?, t: Throwable?) {
Log.v("retrofit", "call failed")
}
override fun onResponse(call: Call<NewsResponse>?, response: Response<NewsResponse>?) {
data.value = response!!.body()!!.articles
}
})
return data
}

You're making an asynchronous call, so data.value will not be set until that asynchronous call resolves. However, since you are generating a MutableLiveData, you should be able to observe, which will give you an update when your asynchronous call sets the value.

Just use object:Callback
accessTocken.enqueue(object : Callback<AccessToken> {
override fun onFailure(call: Call<AccessToken>, t: Throwable) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onResponse(call: Call<AccessToken>, response: Response<AccessToken>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})

Try
fun getNews(code: String): LiveData<List<News>>{
val call = service.getNewsByCountry(code, Constant.API_KEY)
var data = MutableLiveData<List<News>>()
doAsync {
call.enqueue(object : Callback<NewsResponse> {
override fun onFailure(call: Call<NewsResponse>?, t: Throwable?) {
Log.v("retrofit", "call failed")
}
override fun onResponse(call: Call<NewsResponse>?, response: Response<NewsResponse>?) {
data.value = response!!.body()!!.articles
}
})
}
return data
}
If not exists doAsync try add follow anko dependency on your app/build.gralde
implementation "org.jetbrains.anko:anko-design:0.10.5"

Here I found a more extensive answer to your question on this article.
Before retrofit 2.6.0 you have to call enqueue() and implement callbacks. Now it is noτ necessary anymore.
You should change from this:
class TodoRepository {
var client: Webservice = RetrofitClient.webservice
fun getTodo(id: Int): LiveData<Todo> {
val liveData = MutableLiveData<Todo>()
client.getTodo(id).enqueue(object: Callback<Todo>{
override fun onResponse(call: Call<Todo>, response: Response<Todo>) {
if (response.isSuccessful) {
// When data is available, populate LiveData
liveData.value = response.body()
}
}
override fun onFailure(call: Call<Todo>, t: Throwable) {
t.printStackTrace()
}
})
// Synchronously return LiveData
// Its value will be available onResponse
return liveData
}
}
to this:
class TodoRepository {
var client: Webservice = RetrofitClient.webservice
suspend fun getTodo(id: Int) = client.getTodo(id)
}
Here you have the complete answer -> https://proandroiddev.com/suspend-what-youre-doing-retrofit-has-now-coroutines-support-c65bd09ba067

Related

How to use nested suspend in Kotlin android

I have this function in Kotlin:
class DictionaryWorker constructor(
context: Context,
private val workerParameters: WorkerParameters,
private val apiInterface: ApiInterface
) :
KneuraWorker(context, workerParameters), BaseDataSource {
private var isJobSuccess: Boolean = false
override suspend fun doWorkerJob(): Result = withContext(Dispatchers.IO) {
val call = apiInterface.downloadDictionaryFille(DICTIONARY_FILE_URL)
call!!.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
} else {
Log.d("TAG", "server contact failed")
isJobSuccess = false
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) { }
})
return#withContext if (isJobSuccess)
Result.success()
else
Result.failure()
}
}
What is currently happening:
Before this block-1 below
call!!.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
} else {
Log.d("TAG", "server contact failed")
isJobSuccess = false
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) { }
})
This block-2 executes
return#withContext if (isJobSuccess)
Result.success()
else
Result.failure()
What I am trying to do
Make sure only after block 1 is executed block 2 is executed
Not sure what call!!.enqueue() does, but it's quite likely that it starts another thread and performs it's work asynchronously.
So block 2 is not waiting till block 1 is done.
A really ugly way (which I don't recommend) handling this would be using a CountDownLatch.
But I'd rather add a callback to doWorkerJob():
override fun doWorkerJob(callback: (Result) -> Unit) {
val call = apiInterface.downloadDictionaryFille(DICTIONARY_FILE_URL)
if (call == null) {
callback(Result.failure())
}
call?.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
callback(Result.success())
} else {
Log.d("TAG", "server contact failed")
callback(Result.failure())
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) {
callback(Result.failure())
}
})
}

Methods returns a null value before Retrofit Callbacks are executed

When working with the MVVM pattern in Android developments, we create a repository class where we execute all the network requests. The problem is since retrofit's .enqueue() method is asynchronous, my method that calls .enqueue doesn't wait until the callback is obtained(which is pretty logical) and returns null.
One way to solve this problem is to pass MutableLiveData object to my repository method and set its value in the callback, but I don't want to observe all my ViewModel properties in my view(fragment).
What is the common way to solve this problem?
fun createRoute(newRoute: RouteToSend): String {
var responseMessage: String? = null
webService.createRoute(authToken!!, newRoute).enqueue(object: Callback<Message> {
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let { responseMessage = it.message }
}
})
return responseMessage!!
}
Pass a callback as an argument, e.g.
createRoute(newRoute: RouteToSend, callback: CreateRouteListener)
with
interface CreateRouteListener {
fun onFailure()
fun onResponse(response: String)
}
and call the corresponding method when the async process finishes:
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
callback.onFailure()
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let {
responseMessage = it.message
callback.onResponse(responseMessage)
}
}
Calling createRoute will then look like this:
createRoute(RouteToSend(), object: CreateRouteListener {
override fun onFailure() {
// handle failure
}
override fun onResponse(response: String) {
// handle response
}
}
Yes, using MutableLiveData is one way, on the other hand using callback mechanism is another and more suitable way.
If you want to use callbacks you can change your method like
fun createRoute(newRoute: RouteToSend, callback : (String?) -> Unit): String {
var responseMessage: String? = null
webService.createRoute(authToken!!, newRoute).enqueue(object: Callback<Message> {
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
callback(responseMessage)
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let { responseMessage = it.message
callback(responseMessage)}
}
})
}
then you can call your createRoute method like this
createRoute(route_to_send_variable,
callback = {
it?.let {
// use the response of your createRoute function here
}
})

How to make synchronous call in Coroutine

I want to make my network request synchronous because the input of second request comes from the output of first request.
override fun onCreate(savedInstanceState: Bundle?) {
retrofit1 =Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/").addConverterFactory(GsonConverterFactory.create()).build()
retrofit2 =Retrofit.Builder()
.baseUrl("https://samples.openweathermap.org/").addConverterFactory(GsonConverterFactory.create()).build()
button.setOnClickListener { view ->
CoroutineScope(IO).launch {
fakeApiRequest()
}}
In my fakeApiRequest(),I am making two network request.
private suspend fun fakeApiRequest() {
val result1 :Geo?= getResult1FromApi()
val result2: Long? = getResult2FromApi(result1)}
Since,this is an asynchronous call,I am getting Null Pointer Exception in my getResult2FromApi(result1) method because the argument passed is null.
In order to fix this issue,I had to add delay(1500) in first call.
private suspend fun getResult1FromApi(): Geo? {
val service:CallService = retrofit1!!.create(CallService::class.java)
val call = service.getUsers()
call.enqueue(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
g = users.get(0).address.geo
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
}
})
delay(1500)
return g
}
-----------------------------------------------------------
private suspend fun getResult2FromApi(result1: Geo?): Long? {
val service2:CallService = retrofit2!!.create(CallService::class.java)
val call2 = service2.getWeather(result1?.lat!!, result1.lng,"b6907d289e10d714a6e88b30761fae22")
call2.enqueue(object : Callback<WeatherData> {
override fun onResponse(call: Call<WeatherData>, response: Response<WeatherData>) {
}
override fun onFailure(call: Call<WeatherData>, t: Throwable) {
}
})
return dt
}
Is there anyway I can make this synchronous, so that I don't have to pass any delay time.
You haven't implemented the suspendable function correctly. You must use suspendCoroutine:
suspend fun getResult1FromApi(): Geo? = suspendCoroutine { continuation ->
val service = retrofit1!!.create(CallService::class.java)
service.getUsers().enqueue(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
continuation.resume(response.result.getOrNull(0)?.address?.geo)
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
Now your function is synchronous and returns a Geo object.

Call retrofit inside other mathod and return result to main thread

write this code :
fun getStoreTitles():List<sample> {
var responseResult:List<sample>
responseResult= listOf(sample("","",""))
val service = getRetrofitInstance()!!.create(GetDataService::class.java)
val call = service.getAllPhotos()
call.enqueue(object : Callback<List<sample>> {
override fun onResponse(call: Call<List<sample>>, response: Response<List<sample>>) {
responseResult=response.body()!!
var t=0
}
override fun onFailure(call: Call<List<sample>>, t: Throwable) {
/*progressDoalog.dismiss()*/
//Toast.makeText(this#MainActivity, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show()
}
});
return responseResult
}
and want to call that method from main activity with this way:
var responseResult:List<sample>
val FrameWork=StoreTitle()
responseResult=FrameWork.getStoreTitles()
when run the app,retrofit run the successful but nothing return to the responseResult and that is null,i think retrofit run other thread and that's reason.how can i solve that problem?
Update your api call method:
fun getStoreTitles(callback : Callback<List<sample>>) {
var responseResult:List<sample>
responseResult= listOf(sample("","",""))
val service = getRetrofitInstance()!!.create(GetDataService::class.java)
val call = service.getAllPhotos()
call.enqueue(callback);
}
you have to call like this :
val FrameWork=StoreTitle()
FrameWork.getStoreTitles(object : Callback<List<sample>> {
override fun onResponse(call: Call<List<sample>>, response: Response<List<sample>>) {
val responseResult : List<sample>? =response.body()
//handle your success
}
override fun onFailure(call: Call<List<sample>>, t: Throwable) {
//handle your failure
}
})

Kotlin lambda function with arguments

I am currently developing an Android application that uses the Retrofit library for REST api usage.
For instance, I have the following code from MainActivity.kt :
fun userLogin(){
calls.userLogin() { updateUiComponents() }
}
fun updateUiComponents(){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}
And I have in a separate file the definition of the Retrofit calls:
fun userLogin(postActionMethod: () -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod()
}
})
}
After the Retrofit call is implemented and it is successful, reaching the onResponse method, I would like to send the Response object as a parameter of the lambda function back to the MainAcativity.kt. From the MainActivity.kt, the lambda function would use this information to perform some specific task.
Is that a way of defining a lambda function like this, with arguments? If it is the case, how can I pass the lambda function as a parameter like done on the following line:
calls.userLogin(body) { updateUiComponents() }
Thank u!
I don't know if I get what your problem is but a lambda does not need to do not have any parameter. You can easily do something like
fun userLogin(postActionMethod: (Response<LoginResponse>?) -> Unit){
val call = service.userLogin()
call.enqueue(object : Callback<LoginResponse>{
override fun onFailure(call: Call<LoginResponse>?, t: Throwable?) {
Log.i("ERROR RUNNING CALL", t?.message.toString())
}
override fun onResponse(call: Call<LoginResponse>?, response: Response<LoginResponse>?) {
postActionMethod(response)
}
})
}
so you consume it with
fun userLogin(){
calls.userLogin() { updateUiComponents(it) }
}
fun updateUiComponents(response: Response<LoginResponse>?){
Toast.makeText(applicationContext, "LAMBDA EXECUTED",Toast.LENGTH_SHORT).show()
}

Categories

Resources