70

current code:

Retrofit retrofit = new Retrofit.Builder()
                  .baseUrl(Constant.BASEURL)
                  .addConverterFactory(GsonConverterFactory.create())
                  .build();

APIService service = retrofit.create(APIService.class);

Call<ResponseWrap> call = service.getNewsData();

call.enqueue(new Callback<ResponseWrap>() {

  @Override
  public void onResponse(Call<ResponseWrap> call1, Response<ResponseWrap> response) {
    if (response.isSuccess()) {

        ResponseWrap finalRes = response.body();
        for(int i=0; i<finalRes.getResponse().getResults().size(); ++i){
            String title = finalRes.getResponse().getResults().get(i).getWebTitle();
            News n = new News(titleCategory, title, null);
            newsList.add(n);
        }

        AdapterRecommendation adapter = new AdapterRecommendation(getApplicationContext(), newsList);
        listView.setAdapter(adapter);

    }
    else{
        Toast.makeText(getApplicationContext(), "onResponse  - something wrong" + response.message(), Toast.LENGTH_LONG).show();
    }
  }

  @Override
  public void onFailure(Call<ResponseWrap> call1, Throwable t) {
      Toast.makeText(getApplicationContext(), "exception: " + t.getMessage(), Toast.LENGTH_LONG).show();
  }
});

works fine.

Now i want to make multiple calls (number of call will be decided at run time) and all calls gives data in same format. data from all calls needs to be add to newsList. Once data is available from all calls and added to newsList, call

AdapterRecommendation adapter = new AdapterRecommendation(getApplicationContext(), newsList);
listView.setAdapter(adapter);

Can anyone help me what is the best way to get data from multiple calls and wait until all request is not over in retrofit 2.0.

Devesh Agrawal
  • 8,982
  • 16
  • 82
  • 131

5 Answers5

77

The clean and neat approach to wait until all your requests will be done is to use Retrofit2 in conjunction with RxJava2 and its zip function.

What zip does is basically constructs new observable that waits until all your retrofit Observable requests will be done and then it will emit its own result.

Here is an example Retrofit2 interface with Observables:

public interface MyBackendAPI {
  @GET("users/{user}")
  Observable<User> getUser(@Path("user") String user);

  @GET("users/{user}/photos")
  Observable<List<Photo>> listPhotos(@Path("user") String user);

  @GET("users/{user}/friends")
  Observable<List<User>> listFriends(@Path("user") String user);
}

In the code where you going to make multiple requests and only after all of them will complete do something else you can then write the following:

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build();

    MyBackendAPI backendApi = retrofit.create(MyBackendAPI.class);

    List<Observable<?>> requests = new ArrayList<>();

    // Make a collection of all requests you need to call at once, there can be any number of requests, not only 3. You can have 2 or 5, or 100.
    requests.add(backendApi.getUser("someUserId"));
    requests.add(backendApi.listPhotos("someUserId"));
    requests.add(backendApi.listFriends("someUserId"));

    // Zip all requests with the Function, which will receive the results.
    Observable.zip(
            requests,
            new Function<Object[], Object>() {
                @Override
                public Object apply(Object[] objects) throws Exception {
                    // Objects[] is an array of combined results of completed requests

                    // do something with those results and emit new event
                    return new Object();
                }
            })
            // After all requests had been performed the next observer will receive the Object, returned from Function
            .subscribe(
                    // Will be triggered if all requests will end successfully (4xx and 5xx also are successful requests too)
                    new Consumer<Object>() {
                        @Override
                        public void accept(Object o) throws Exception {
                            //Do something on successful completion of all requests
                        }
                    },

                    // Will be triggered if any error during requests will happen
                    new Consumer<Throwable>() {
                        @Override
                        public void accept(Throwable e) throws Exception {
                            //Do something on error completion of requests
                        }
                    }
            );

That's all :)


Just in case wanna show how the same code looks like in Kotlin.

    val retrofit = Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build()

    val backendApi = retrofit.create(MyBackendAPI::class.java)

    val requests = ArrayList<Observable<*>>()

    requests.add(backendApi.getUser())
    requests.add(backendApi.listPhotos())
    requests.add(backendApi.listFriends())

    Observable
            .zip(requests) {
                // do something with those results and emit new event
                Any() // <-- Here we emit just new empty Object(), but you can emit anything
            }
            // Will be triggered if all requests will end successfully (4xx and 5xx also are successful requests too)
            .subscribe({
                //Do something on successful completion of all requests
            }) {
                //Do something on error completion of requests
            }
Клаус Шварц
  • 3,158
  • 28
  • 44
  • 4
    android.os.NetworkOnMainThreadException happen, do you have idea about this – Sandun Priyanka Oct 16 '18 at 16:21
  • 1
    @SandunPriyanka yes I do. Check this answer: https://stackoverflow.com/a/6343299/1824898 – Клаус Шварц Oct 19 '18 at 09:40
  • 2
    I did, I enable two behaviors to .zip, .subscribeOn(Schedulers.io()) .observeOn(Schedulers.newThread()), After it gone away, your answer save my more hours. – Sandun Priyanka Oct 20 '18 at 06:25
  • Thanks for the response, How we identify different request response object from Object[], any procedure for rollback some API(transactions) – Sandun Priyanka Oct 20 '18 at 18:22
  • 3
    @SandunPriyanka I forgot that my answer was related to retrofit. So yes, observeOn/subscribeOn should do the trick. There is another answer here: https://stackoverflow.com/a/44007611/1824898 – Клаус Шварц Oct 22 '18 at 14:23
  • 1
    @SandunPriyanka something like `instance of` (Java) or `is` (Kotlin) will help you to identify the objects you are receiving are of correct kind. Rollbacks and transactions are not related to networking at all. I would recommend you to read more on reactive programming patterns in general. – Клаус Шварц Oct 22 '18 at 14:25
  • I am using retrofit:2.0.0-beta2 . Can I use this RxJava2 – Mansuu.... Jan 15 '19 at 09:22
  • 1
    check it out https://github.com/fakefacebook/Retrofit-2-with-Rxjava-multiple-request – Nikunj Paradva May 03 '19 at 10:23
  • hey i didn't got the execution flow of .zip method like after getting response in Object[] you said "do something with those results and emit new event" where will it point to after returning objects and where can i catch the returned object and when subscribe method will execute – abhishek Jul 30 '19 at 13:07
  • use .subscribeOn(Schedulers.io().observeOn(AndroidSchedulers.mainThread()) – Abhishek Dubey Oct 21 '19 at 05:54
  • 1
    Cannot resolve method 'zip(java.util.List>, anonymous io.reactivex.functions.Function)' , You have any idea what is the reason? – rav Nov 22 '19 at 10:12
  • Isnt this solution executing the network calls twice? Once when you invoke the backendApi.getUser() method by adding it to the list, and then again within Observable.zip().. as shown here: https://stackoverflow.com/questions/71890000/rxjava2-observable-ziplist-executes-network-calls-twice – Eugenio Lopez Apr 16 '22 at 17:35
3

If you don't mind adding one more dependency you could use RxAndroid. In particular, you should change your Service interface with something similar to this:

@GET("/data")
Observable<ResponseWrap> getNewsData();

Now, you can do this:

Observable
            .range(0, **numberOfTimes**, Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnError(new Action1<Throwable>() {
                @Override
                public void call(Throwable throwable) {
                    Log.e("error", throwable.toString());
                }
            })
            .concatMap(new Func1<Integer, Observable<ResponsWrapper>>() {
                @Override
                public Observable<ResponsWrapper> call(Integer integer) {
                    Log.i("news", "nr:" + integer);
                    //Does the call.
                    return service.getNewsData(integer);
                }
            }).concatMap(new Func1<ResponsWrapper, Observable<News>>() {
        @Override
        public Observable<News> call(final ResponsWrapper responsWrapper) {
            return Observable.fromCallable(new Func0<News>() {
                @Override
                public News call() {
                    //change the result of the call to a news.
                    return new News(responsWrapper.category,responsWrapper.title,null);
                }
            });
        }
    }).toList().subscribe(new Action1<List<News>>() {
        @Override
        public void call(List<News> newList) {
           AdapterRecommendation adapter = new AdapterRecommendation(getApplicationContext(), newsList);
           listView.setAdapter(adapter);
        }
    });

Just change numberOfTimes and it will work! Hope it helps.

P.s. maybe there are cleaner ways to do this.

Jibbo
  • 442
  • 3
  • 13
  • where can i find sample for Observable in retrofit2? – iSrinivasan27 Sep 19 '16 at 06:29
  • 1
    I really like this piece: http://www.captechconsulting.com/blogs/a-mvp-approach-to-lifecycle-safe-requests-with-retrofit-20-and-rxjava – Jibbo Sep 30 '16 at 07:51
  • I am using Retrofit and RxJava in my project I have around 4 APIs of GET fetching data from 4 tables from the server and have to insert in the SQLite tables using ActiveAndroid ORM. I want to show a progress bar while downloading the data until it completes and inserted locally. Local table count must be equal to server table count each table count. I also want to call each API in a specific order on each API response to next one. Which RxJava and Retrofit library keywords, operators and filters should be used? – hasnain_ahmad May 23 '18 at 11:33
  • @hasnain_ahmad if you have found a solution can you please share it here as I am also trying to do the same. Thanks – bhaskar Dec 07 '18 at 05:25
1

You can achieve it by making synchronous retrofit calls. To avoid NetworkOnUiException, I am doing this inside asynctask.

   List<Something> list = new ArrayList();

public void doInBackground(){
    for(int i = 0; i < numberOfCalls; i++){
        Call<Something> call = service.method1("some_value");
        List<Something> list = call1.execute().body();
        list.add(list1);        
    }
}

public void onPostExecute(){
    AdapterRecommendation adapter = new AdapterRecommendation(getApplicationContext(), newsList);
    listView.setAdapter(adapter);
}

This will ensure that the second call happens only after the first one has completed.

If you are using rx-java, you can use Zip/flatMap operator as used in this answer.

Ankit Aggarwal
  • 5,317
  • 2
  • 30
  • 53
1

Here is a solution based on kotlin coroutines.

//turn the request methods into suspend functions
@GET("data1")
suspend fun getData(): Response<Data1>
@GET("data2")
suspend fun getData2(): Response<Data2>

//define a data class to ecapsulate data from several results
class Data{
    val data1: Data1,
    val data2: Data2
}

//generic class to encapsulate any request result
sealed class Result<out T : Any?> {
    data class Success<out T : Any?>(val data: T) : Result<T>()
    data class Error(val message: String, val exception: Exception?) : Result<Nothing>()
}

scope.launch {
    val result = withContext(Dispatchers.IO) {
        try {
            //start two requests in parallel
            val getData1Task = async { webservice.getData1() }
            val getData2Task = async { webservice.getData2() }
            //await for both to finish
            val data1Response = getData1Task.await()
            val data2Response = getData2Task.await()
            //process the response
            if (data1Response.isSuccessful && data2Response.isSuccessful)
                Result.Success(Data(data1Response.body()!!,data2Response.body()!!))
            else
                Result.Error("server error message", null)
        } catch (e: Exception) {
            Result.Error(e.message.orEmpty(), e)
        }
    }
    //main thread
    result.run {
        when (this) {
            is Result.Success -> { 
                //update UI
            }
            is Result.Error -> {
                toast(message)
                log(message)
            }
        }
    }
}
yaugenka
  • 2,602
  • 2
  • 22
  • 41
0

for anybody checking this question. This works for me (Kotlin)

fun manyRequestsNetworkCall(requests: ArrayList<Observable<*>>, activity: Activity){
    Observable.zip(requests){results ->
        activity.runOnUiThread(Runnable {
           //do something with those results
           // runOnUiThread solves the problem cannot do something on background thread
        })
      // observeOn and subscribeOn solvesthe problem of NetworkOnMainThreadException
    }.observeOn(AndroidSchedulers.mainThread())
        .subscribeOn(Schedulers.io())
        .doOnSubscribe { userWorkdysResponse.value = Response.loading((requestType)) }
        .subscribe ({
           // do something when all the requests are done
        },{
           // do something if there is an error
        })
}