Returning a value after callback in Kotlin

8.2k views Asked by At

How can I return a value after a callback in kotlin, I tried using Thread.sleep but it doesn't work

   fun searchColorFromAPI(): Colors {
    val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
    val result: MutableList<String> = arrayListOf()
    val call: Call<Colors?>? = service.unityConverter(result)
    call?.enqueue(object : Callback<Colors?> {
        override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
            //switchProgressVisibility()
            if (response.isSuccessful) {
                val serviceResponse: Colors? = response.body()

                if (serviceResponse != null) {
                    mColors = serviceResponse

                }
                else {
                    //buildToast(getString(R.string.null_response))
                }
            }

            else {
                //buildToast(getString(R.string.response_unsuccessful))
                val errorBody: ResponseBody = response.errorBody()
                Log.e(TAG, errorBody.toString())
            }
        }

        override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
           /* buildToast(getString(R.string.error_calling_service))
            Log.e(TAG, t?.message)*/
        }
    })

    return mColors
}

Always, the mColors is returned before the onFailure or onResponse because they're asynchronous. Before this code was in MainActivity but I was advised to take off, but now when I try get mColors I get the empty value before and after the onResponse is executed, please I'm still learning Kotlin and Android.

2

There are 2 answers

0
mightyWOZ On BEST ANSWER

Your problem stems from the fact that Retrofit call is asynchronous, so as soon as you call searchColorFromAPI it returns you mColors but the API call may not have been made yet, so you get the mColors value before API call.

To solve this issue, you can do

  1. Use callback, this will require little modification in your current setup, but the 2nd option is preferable over this. Using callback your function should look like this.

    /* Now instead of returning a value, your function takes a function (named callback) 
       as parameter. when your api call finishes, you can call the callback function and 
       pass the api response.
     */
    fun searchColorFromAPI(callback: (Colors?) -> Unit) {
        val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
        val result: MutableList<String> = arrayListOf()
        val call: Call<Colors?>? = service.unityConverter(result)
        call?.enqueue(object : Callback<Colors?> {
            override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
                //switchProgressVisibility()
                if (response.isSuccessful) {
                    val serviceResponse: Colors? = response.body()
                    /** pass API response to callback */
                    callback(serviceResponse)
                }
                else {
                    val errorBody: ResponseBody = response.errorBody()
                    Log.e(TAG, errorBody.toString())
                    callback(null)
                }
            }
    
            override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
                callback(null)
            }
        })
    }
    

And in your activity declare a function as follows.

// This function will be called when your api call finishes
// and it will give you the api response
fun apiCallback(colors: Colors?){
      if(colors == null){
        // API Call failed
    }
    else{
        // use colors as returned by API
    }
}

And now call to searchColorFromApi should look like this

searchColorFromApi(apiCallback)
  1. Use Live Data, declare following field in your viewmodel, if you are not using viewmodel then declare it in the class which has searchColorFromApi function.

    var colors: MutableLiveData<Colors> = MutableLiveData()
    

and modify your searchColorFromAPI function as follows

fun searchColorFromAPI() {
        val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
        val result: MutableList<String> = arrayListOf()
        val call: Call<Colors?>? = service.unityConverter(result)
        call?.enqueue(object : Callback<Colors?> {
            override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
                //switchProgressVisibility()
                if (response.isSuccessful) {
                    val serviceResponse: Colors? = response.body()
                    if (serviceResponse != null) {
                       colors.postValue(response.body)
                    }
                }
                else {
                    colors.postValue(null)
                    val errorBody: ResponseBody = response.errorBody()
                    Log.e(TAG, errorBody.toString())
                }
            }

            override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
                colors.postValue(null)
            }
        })
    }

and in your activity do following

  fun setupObservers(){
       yourApiCallingClass.colors.observe(this, Observer {
        // this code is called when ever value of color field changes
       })
   }
1
Ashok Kumar On
  1. You can use live data ,that gets updated once the callback receives ,the same live data is observed by the caller fragment/activity

  2. You can use coroutines to return a value from function which has asyn calls in it.

  3. You can use interface callbacks to activity/ fragment to trigger the updates received from retrofit calls.