Как сделать несколько запросов и ждать, пока данные будут получены из всех запросов в версии 2.0 - android

текущий код:

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();
  }
});

работает отлично.

Теперь я хочу сделать несколько вызовов (количество вызовов будет определено во время выполнения), и все вызовы выдают данные в том же формате. данные из всех вызовов необходимо добавить в список новостей. Когда данные доступны из всех вызовов и добавлены в список новостей, вызовите

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

Может ли кто-нибудь помочь мне, что является лучшим способом получить данные от нескольких вызовов и ждать, пока весь запрос не будет завершен в версии 2.0.

Ответы

Ответ 1

Чистый и аккуратный подход подождать, пока все ваши запросы будут выполнены, - это использовать Retrofit2 вместе с RxJava2 и его zip функцией.

То, что делает zip, в основном создает новые наблюдаемые, которые ждут, пока все ваши модифицированные запросы Observable не будут выполнены, а затем он испустит свой собственный результат.

Вот пример интерфейса Retrofit2 с 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);
}

В коде, где вы собираетесь делать несколько запросов, и только после того, как все они завершат что-то еще, вы можете написать следующее:

    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
                        }
                    }
            );

Все это :)


На всякий случай хочу показать, как выглядит тот же код в 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
            }

Ответ 2

Вы можете добиться этого, совершив синхронные переадресации вызовов. Чтобы избежать NetworkOnUiException, я делаю это внутри 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);
}

Это гарантирует, что второй вызов произойдет только после завершения первого.

Если вы используете rx-java, вы можете использовать оператор Zip/flatMap, используемый в этом ответе.

Ответ 3

Если вы не возражаете добавить еще одну зависимость, вы можете использовать RxAndroid. В частности, вы должны изменить свой Сервисный интерфейс с чем-то похожим на это:

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

Теперь вы можете сделать это:

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);
        }
    });

Просто измените числоOfTimes, и это сработает! Надеюсь, поможет.

Ps, возможно, есть более чистые способы сделать это.