web-dev-qa-db-fra.com

Gérer la pagination avec RxJava

J'utilise Retrofit + RxJava sur une application Android et je me demande comment gérer la pagination de l'API pour enchaîner les appels jusqu'à ce que toutes les données soient récupérées. Quelque chose comme ceci:

Observable<ApiResponse> getResults(@Query("page") int page);

L'objet ApiResponse a une structure simple:

class ApiResponse {
    int current;
    Integer next;
    List<ResponseObject> results;
}

L'API renverra une valeur suivante jusqu'à la dernière page.

Il existe un bon moyen d'y parvenir? J'ai essayé de combiner certains flatMaps () , mais sans succès.

28
Rafael Toledo

Vous pouvez le modéliser récursivement:

Observable<ApiResponse> getPageAndNext(int page) {
  return getResults(page)
      .concatMap(new Func1<ApiResponse, Observable<ApiResponse>>() {

        @Override
        public Observable<ApiResponse> call(ApiResponse response) {
          // Terminal case.
          if (response.next == null) {
            return Observable.just(response);
          }
          return Observable.just(response)
              .concatWith(getPageAndNext(response.next));
        }

      });
}

Ensuite, pour le consommer,

getPageAndNext(0)
    .concatMap(new Func1<ApiResponse, Observable<ResponseObject>>() {

        @Override
        public Observable<ResponseObject> call(ApiResponse response) {
          return Observable.from(response.results);
        }

    })
    .subscribe(new Action1<ResponseObject>() { /** Do something with it */ });

Cela devrait vous fournir un flux de ResponseObject qui arrivera dans l'ordre, et arrivera probablement en morceaux de la taille d'une page.

55
lopar

Iopar a donné un excellent exemple.

Juste un petit ajout.
Si vous souhaitez obtenir toutes les pages en un seul appel onNext ().
Cela peut être utile lorsque vous voulez Zip ce résultat avec un autre observable.
Vous devez écrire:

private List<String> list = new LinkedList() {
    {
        add("a");
        add("b");
        add("c");
    }
};

int count = 1;

public Observable<List<String>> getAllStrings(int c) {
    return Observable.just(list)
            .concatMap(
                    strings -> {
                        if (c == 3) {
                            return Observable.just(list);
                        } else {
                            count += 1;
                            return Observable.Zip(
                                    Observable.just(list),
                                    getAllStrings(count),
                                    (strings1, strings2) -> {
                                        strings1.addAll(strings2);
                                        return strings1;
                                    }
                            );
                        }
                    }
            );
}

Utilisations :

getAllStrings(0)
        .subscribe(strings -> {
            Log.w(TAG, "call: " + strings);
        });

et vous obtiendrez:

call: [a, b, c, a, b, c, a, b, c, a, b, c]
3
Yvgen

J'ai répondu à ma solution dans un article similaire: https://stackoverflow.com/a/34378263/1437

L'astuce ou l'amendement à la solution fournie par @Iopar est l'inclusion d'un "déclencheur" observable qui peut être émis de diverses manières.

Dans le code que j'ai publié, il est émis une fois qu'une page complète d'éléments a été traitée, mais cela peut également se produire si un utilisateur clique sur un bouton/fait défiler.

1
Setheron