Quentin Tarantino
Quentin Tarantino

Reputation: 57

How to make a method wait till its retrofit method got its response before return?

How to make a function wait till a retrofit method in it got its response before return?

I've seen lots of questions and answers on Stack Overflow, but because of being stupid or what I am not totally getting them.

I have a view model, which gives view a text to show on a screen.

Like, I do like this in view, activity_foo.xml :

android:text = "@{fooViewModel.getUserName()}"

and in a view model class :

fun getUserName() : String = fooRepository.getUserName()

I think you'll get the strategy because it's common in MVVM pattern.

So FooRepository, I mean my real repository for a view model looks like this :

class MainRepository(private val userApi: UserApi) {
    fun getUserList() : ArrayList<User>? {
        var userList : ArrayList<User>? = null
        userApi.getAllUser().enqueue(object : Callback<ArrayList<User>> {
            override fun onResponse(call: Call<ArrayList<User>>, response: Response<ArrayList<User>>) {
                if (response.body() != null) {
                    Log.e("user list succeed", response.body()!!.toString())
                    userList = response.body()!!
                    Log.e("user list saved", userList!![0].id)
                }
            }

            override fun onFailure(call: Call<ArrayList<User>>, t: Throwable) {
                Log.e("user list fail", t.message!!)
            }
        })
        Log.e("this log", "shouldn't be above the logs in onResponse ")

        return userList
    }
}

This is a usual, typical way to get information from a server using retrofit I think, right?

View calls a method in its View Model to get what to show

and view model calls a method in its Repository to get data, which is userList in that code.

I sure get the data I need. The response.code().toString() works fine

response.body()!!.toString() works fine, everything works fine. The body contains just what I expect.

And the second log works fine too.

But the problem is,

This function returns null at the end whatever that body contains.

I know it is because that enqueue runs asynchronously,

so the method returns null before onResponse method responds and save data to userList.

The proof is that third log which is executed right before returning is above all the logs in onResponse.

So how can I make this method wait for onResponse finishes its job before return?

or, how can I make View get a string onResponse made even after the method's returning?

I tried execute() but it's not working because it can't and shouldn't run on the main thread and also android rejects it.

Thank you for all of your comments and answers.

Upvotes: 1

Views: 1567

Answers (2)

Hagar Magdy
Hagar Magdy

Reputation: 313

Why don't you try using live data?

So your view model will contain this instead of your function

fun getUserName() : LiveData<ResponseBody> = fooRepository.getUserName()

And your repository will be like that

class MainRepository() {
fun getUserList() : LiveData<ResponseBody> {
    MutableLiveData<ResponseBody> data = new MutableLiveData<>();

    var userList : ArrayList<User>? = null
    userApi.getAllUser().enqueue(object : Callback<ResponseBody> {
        override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
            if (response.body() != null) {
                 data.postValue(response.body().);

            }
        }

        override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
            data.postValue(null);
        }
    })
    Log.e("this log", "shouldn't be above the logs in onResponse ")
}

}

ReponseBody class will contain your response from api

Finally in your activity

 mainViewModel.getUserList().observe(this,
        Observer<ApiResponse> { apiResponse ->
            if (apiResponse != null) {
                // handle your response in case of success
            }
             else {
                // call failed.

            }
        })

Upvotes: 3

Ilia Kuzmin
Ilia Kuzmin

Reputation: 165

In general, you need to implement some kind of callback when onResponse is called.

class MainRepository(private val userApi: UserApi) {
    fun getUserList(resultCallback : ResultCallback) {
        var userList : ArrayList<User>? = null
        userApi.getAllUser().enqueue(object : Callback<ArrayList<User>> {
            override fun onResponse(call: Call<ArrayList<User>>, response: Response<ArrayList<User>>) {
                if (response.body() != null) {
                    Log.e("user list succeed", response.body()!!.toString())
                    resultCallback.onResult(response.body()!!)

                }
            }

            override fun onFailure(call: Call<ArrayList<User>>, t: Throwable) {
                Log.e("user list fail", t.message!!)
                resultCallback.onFailure(t.message!!)
            }
        })
        Log.e("this log", "shouldn't be above the logs in onResponse ")
    }
}

Then somewhere in your code pass this callback and wait for result and do with result something (e.g. show on RecyclerView). I don't know how to it's should work with MVVM but this is common approach to listen async operation.

You can also use RxJava to achieve that behaviour

Upvotes: 1

Related Questions