Batch loading с Retrofit и RxJava

в 13:09, , рубрики: android, reactive programming, retrofit, rxjava, Разработка под android

Доброго времени суток! Работая над одним проектом, обнаружил, что через связку retrofit2 и retrofit2 adapter-rxjava нельзя реализовать batch loading в одном потоке.

Приведу пример. Имеем описание retrofit-сервиса:

interface Api {
    @GET("query.json")
    Observable<List<SomeEntityServerView>> getAll(
        @Query("first") int first, 
        @Query("max") int batchSize);
}

Загрузка одного пакета:

service.getAll(0,20).map(list -> ...).observeOn(...).subscribe(...);

Для загрузки всех сущностей с сервера, нам придется каждый раз создавать новый observable для загрузки очередного пакета. Кроме того, как запихнуть все эти observable'ы в один поток, представляется крайне сложным.

Для решения это проблемы, предлагаю достаточно простой подход.

public class BatchLoadingUtils {

    /**
     * @param batchLoaderFactory - метод создания обсервабла по номеру первого элемента
     * @param batchSize - размер пакета
     */
    public static <T> Observable<List<T>> create(Func1<Integer, Observable<List<T>>> batchLoaderFactory, int batchSize) {
        //Здесь храним номер первого элемента в пакете
        AtomicInteger first = new AtomicInteger(0);
        //Сюда будем этот номер отправлять. Соответственно, первый номер - 0. При желании, можно вынести его в параметры метода
        BehaviorSubject<Integer> subject = BehaviorSubject.create(0);

        return subject
                //Превращаем смешение в observable
                .flatMap(batchLoaderFactory::call)
                .doOnNext(ts -> {
                    if (ts.size() == batchSize) {
                        //Если загрузили ожидаемое количество элементов, грузим дальше
                        subject.onNext(first.addAndGet(batchSize));
                    } else {
                        //В противном - завершаем работу
                        subject.onCompleted();
                    }
                });
    }
}

Пример использования:

final int batchSize = 10;
BatchLoader
    .create(
        first -> retrofitService.getAll(first, batchSize),
        batchSize
    )
    .observeOn(Schedulers.computation())
    .flatMapIterable(list -> list)
    .map(TimeEntryServerView::buildTimeEntry)
    .buffer(batchSize)
    .subscribe(...);

Таким образом, мы получили достаточно простой и эффективный способ грузить пакеты через ретрофит в связке с RxJava. Гуру RxJava, наверняка смогут предложить более правильный подход, в соответствии со всеми концепциями реактивного программирования и RxJava, однако, такой способ, однозначно, будет понятен всем, кто хоть чуть-чуть знаком с rx.

P.S. Пинайте сильно, это мой первый пост.

Автор: alexander-shustanov

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js