current code:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Constant.BASEURL)
.addConverterFactory(GsonConverterFac
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.
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
}
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.
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
})
}