I'm working with a paginated API. I have used the following solution provided by Adam Millerchip and it works well.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.processors.BehaviorProcessor;
public class Pagination {
// Fetch all pages and return the items contained in those pages, using the provided page fetcher function
public static <T> Flowable<T> fetchItems(Function<Integer, Single<Page<T>>> fetchPage) {
// Processor issues page indices
BehaviorProcessor<Integer> processor = BehaviorProcessor.createDefault(0);
// When an index number is issued, fetch the corresponding page
return processor.concatMap(index -> fetchPage.apply(index).toFlowable())
// when returning the page, update the processor to get the next page (or stop)
.doOnNext(page -> {
if (page.hasNext()) {
processor.onNext(page.getNextPageIndex());
} else {
processor.onComplete();
}
})
.concatMapIterable(Page::getElements);
}
public static void main(String[] args) {
fetchItems(Pagination::examplePageFetcher).subscribe(System.out::println);
}
// A function to fetch a page of our paged data
private static Single<Page<String>> examplePageFetcher(int index) {
return Single.just(pages.get(index));
}
// Create some paged data
private static ArrayList<Page<String>> pages = new ArrayList<>(3);
static {
pages.add(new Page<>(Arrays.asList("one", "two"), Optional.of(1)));
pages.add(new Page<>(Arrays.asList("three", "four"), Optional.of(2)));
pages.add(new Page<>(Arrays.asList("five"), Optional.empty()));
}
static class Page<T> {
private List<T> elements;
private Optional<Integer> nextPageIndex;
public Page(List<T> elements, Optional<Integer> nextPageIndex) {
this.elements = elements;
this.nextPageIndex = nextPageIndex;
}
public List<T> getElements() {
return elements;
}
public int getNextPageIndex() {
return nextPageIndex.get();
}
public boolean hasNext() {
return nextPageIndex.isPresent();
}
}
}
But I have 2 questions :
In this implementation elements are processed at the end (subscribe(System.out::println)) when all pages are loaded. This may cause memory problems if gathered data are numerous. I would prefer to process them (data base save) immediately when they are loaded (in the .doOnNext(page -> { }). I have been able to do it but in a "dirty way" (add database save code in the doOnNext). How can I do this ?
in my implementation of the "page" class I use a custom Gson deserializer. And I don't know how to deal with Generic data. I have had to write "list.add((MyGenericClass)context.deserialize(anArray.getAsJsonObject(), MyGenericClass.class));" where I would want something like "list.add((T)context.deserialize(anArray.getAsJsonObject(), T.class));". How can I keep things realy generic ?
public static JsonDeserializer<Paginator> deserializer = new JsonDeserializer<Paginator>() { @Override public Paginator deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); Paginator paginator = new Paginator(null, Optional.of(1)); if (jsonObject.get("data") != null && !jsonObject.get("data").isJsonNull()) { JsonArray array = jsonObject.getAsJsonArray("data"); List<MyGenericClass> list = new ArrayList<>(); for (JsonElement anArray : array) { list.add((MyGenericClass)context.deserialize(anArray.getAsJsonObject(), MyGenericClass.class)); } paginator.setData(list); } paginator.setCurrent_page(jsonAsInt(jsonObject, "current_page",-1)); paginator.setFrom(jsonAsInt(jsonObject,"from",-1)); paginator.setTo(jsonAsInt(jsonObject,"to",-1)); paginator.setTotal(jsonAsInt(jsonObject,"total",-1)); paginator.setLast_page(jsonAsInt(jsonObject, "last_page", -1)); paginator.setNextPage(); // calculate next page return paginator; } };