Abhijith Konnayil
Abhijith Konnayil

Reputation: 4616

How to wait for an asynchronous method

I need to return the value uId. I am getting the correct value in the first log statement inside the onResponse() function. But when it comes to the return statement it returns null.

I think that onResponse() is running on another thread. If so, how can I make the getNumber() function wait till the onResponse() function finishes execution.(Like thread.join())

Or is there any other solution?

code :

String uId;
public String getNumber() {

    ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
    Call<TopLead> call = apiInterface.getTopLead();
    call.enqueue(new Callback<TopLead>() {
        @Override
        public void onResponse(Call<TopLead> call, Response<TopLead> response) {
            String phoneNumber;
            TopLead topLead = response.body();
            if (topLead != null) {
                phoneNumber = topLead.getPhoneNumber().toString();
                uId = topLead.getUId().toString();
                //dispaly the correct value of uId
                Log.i("PHONE NUMBER, UID", phoneNumber +", " + uId);
                onCallCallback.showToast("Calling " + phoneNumber);
            } else {
                onCallCallback.showToast("Could not load phone number");
            }
        }

        @Override
        public void onFailure(Call<TopLead> call, Throwable t) {
            t.printStackTrace();
        }
    });
    //output: Return uid null
    Log.i("Return"," uid" + uId);
    return uId; 

Upvotes: 6

Views: 15047

Answers (4)

Mattia Ferigutti
Mattia Ferigutti

Reputation: 3708

Since I could not understand the answer of @David I had to come up with a solution myself. I had to receive data from the network using the library OkHttp3. To solve this problem I used LiveData.

fun getText(): LiveData<String> {
    val liveData = MutableLiveData<String>()
    var result = ""
    val client = OkHttpClient()

    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            e.printStackTrace()
            result = e.toString()
            liveData.postValue(result)
        }

        override fun onResponse(call: Call, response: Response) {
            result = response.body.toString()
            liveData.postValue(result)
        }
    })
    return liveData
}

Once you have the LiveData object we can observe it:

getText().observe(viewLifecycleOwner) {
    Toast.makeText(requireContext(), "Received $it", Toast.LENGTH_SHORT).show()
}

Upvotes: 2

ravi
ravi

Reputation: 1001

The problem seems to be that you wanted uId and you are not getting it. Your function getNumber() executes TopDown but the request you are making is Asychronous, runs on a different thread. So at the time that you are returning uId, there is no value in uId. Out of the callbacks that call.enqueue has, and I mean onResponse() and onFailure(), there is no way you can get uId in onFailure, that is obvious but it is not so obvious that you will get uId in `onResponse()'. From the javadoc for Retrofit:

onResponse void onResponse(Call<T> call, Response<T> response)

Invoked for a received HTTP response. Note: An HTTP response may still indicate an application-level failure such as a 404 or 500. Call Response.isSuccessful() to determine if the response indicates success.

So in onResponse you will still need to put some code to ensure you are getting uId and then only return it. And do not return the uId directly, set to some string (global) and access it after making sure it is available.

So change your code like::

public void getNumber() {

ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
Call<TopLead> call = apiInterface.getTopLead();
call.enqueue(new Callback<TopLead>() {
    @Override
    public void onResponse(Call<TopLead> call, Response<TopLead> response) {
        if(response.isSuccesful(){
            String phoneNumber;
        TopLead topLead = response.body();
        if (topLead != null) {
            phoneNumber = topLead.getPhoneNumber().toString();
            uId = topLead.getUId().toString();
            //dispaly the correct value of uId
            Log.i("PHONE NUMBER, UID", phoneNumber +", " + uId);
            onCallCallback.showToast("Calling " + phoneNumber);
            //output: Return uid 
           Log.i("Return"," uid" + uId); 

        } else {
            onCallCallback.showToast("Could not load phone number");
        }
        } else{
            Log.e("in ", " response is not successful" )
        }
    }

    @Override
    public void onFailure(Call<TopLead> call, Throwable t) {
        t.printStackTrace();
    }
});

This still runs on a different thread, so uId will be set after some time. Once set you can use the value. You can also use Synchronous request if you want to run on the main thread and get the uId using call.execute().body(); which returns TopLead object and then you can get uId from ther e using topLead.getUId.toString().

I hope it helps, do ask in comments if you need clarifications.

Upvotes: 1

David
David

Reputation: 361

Your method performs an async request. So the action "return uId; " doesn't wait until your request finishes because they are on different threads.

There are several solutions I can suggest

  1. Use an interface callback

     public void getNumber(MyCallback callback) {
       ...
        phoneNumber = topLead.getPhoneNumber().toString();
        callback.onDataGot(phoneNumber);
     }
    

Your callback interface

     public interface MyCallback {

        void onDataGot(String number);
     }

And finally, calling the method

getNumber(new MyCallback() {
    @Override
    public void onDataGot(String number) {
    // response
    }
});
  1. When using Kotlin (I think it's time for you to use Kotlin instead of Java :))

    fun getNumber(onSuccess: (phone: String) -> Unit) {
      phoneNumber = topLead.getPhoneNumber().toString()
      onSuccess(phoneNumber)
    }
    

Calling the method

    getNumber {
      println("telephone $it")
    }

Upvotes: 9

Ragesh
Ragesh

Reputation: 215

Don't return the value. Because AsyncTask is a separate one runs outside the normal process.

So please create a method with the parameters and pass your values to that method.

Upvotes: 0

Related Questions