web-dev-qa-db-fra.com

Comment faire plusieurs demandes et attendre que les données proviennent de toutes les demandes dans la modification 2.0 - android

code actuel:

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

fonctionne bien.

Maintenant, je veux faire plusieurs appels (le nombre d'appels sera décidé au moment de l'exécution) et tous les appels donnent des données au même format. les données de tous les appels doivent être ajoutées à la liste de nouvelles. Une fois que les données de tous les appels sont disponibles et ajoutées à la liste de nouvelles, appelez

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

Quelqu'un peut-il m'aider quel est le meilleur moyen d'obtenir les données de plusieurs appels et d'attendre que toutes les demandes ne soient pas terminées dans la mise à niveau 2.0?.

39
Devesh Agrawal

L’approche propre et soignée pour attendre que toutes vos demandes soient traitées consiste à utiliser Retrofit2 en association avec RxJava2 et sa fonction Zip.

Qu'est-ce que Zip construit en gros une nouvelle observable qui attend que toutes vos demandes de modification Observable soient effectuées et émet ensuite son propre résultat.

Voici un exemple d'interface Retrofit2 avec 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);
}

Dans le code où vous allez faire plusieurs demandes et seulement après qu'elles auront été complétées, faites autre chose, vous pouvez alors écrire ce qui suit:

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

C'est tout :)


Juste au cas où vous voudriez montrer à quoi ressemble le même code dans 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
            }
41

Vous pouvez y parvenir en effectuant des appels de mise à niveau synchrones. Pour éviter NetworkOnUiException, je le fais dans un asyncte.

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

Cela garantira que le deuxième appel ne se produira qu’après la fin du premier.

Si vous utilisez rx-Java, vous pouvez utiliser l'opérateur Zip/flatMap tel qu'il est utilisé dans this answer.

1
Ankit Aggarwal

Si cela ne vous dérange pas d'ajouter une dépendance supplémentaire, vous pouvez utiliser RxAndroid . En particulier, vous devez modifier votre interface de service avec quelque chose de similaire à ceci:

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

Maintenant, vous pouvez faire ceci:

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

Il suffit de changer numberOfTimes et cela fonctionnera! J'espère que ça aide.

P.s. peut-être y a-t-il des moyens plus propres de le faire.

1
Jibbo