Android:使用repeatWhen进行rx-java无限滚动,takeUntil和带有改进的过滤器

时间:2017-03-01 16:37:59

标签: android rx-java retrofit2 rx-android

我在RxJava上使用Retrofit 2.2。 分页的工作原理如下:我得到第一批数据,我必须要求第二批数据使用相同的参数,除了一个是lastUpdated日期,然后如果我变空或同一批数据,则表示存在没有更多的物品。我找到了关于如何做到的这篇精彩文章https://medium.com/@v.danylo/server-polling-and-retrying-failed-operations-with-retrofit-and-rxjava-8bcc7e641a5a#.40aeibaja。所以我的代码是:

private Observable<Integer> syncDataPoints(final String baseUrl, final String apiKey,
        final long surveyGroupId) {
    final List<ApiDataPoint> lastBatch = new ArrayList<>();
    Timber.d("start syncDataPoints");
    return loadAndSave(baseUrl, apiKey, surveyGroupId, lastBatch)
            .repeatWhen(new Func1<Observable<? extends Void>, Observable<?>>() {
                @Override
                public Observable<?> call(final Observable<? extends Void> observable) {
                    Timber.d("Calling repeatWhen");
                    return observable.delay(5, TimeUnit.SECONDS);
                }
            })
            .takeUntil(new Func1<List<ApiDataPoint>, Boolean>() {
                @Override
                public Boolean call(List<ApiDataPoint> apiDataPoints) {
                    boolean done = apiDataPoints.isEmpty();
                    if (done) {
                        Timber.d("takeUntil : finished");
                    } else {
                        Timber.d("takeUntil : will query again");
                    }
                    return done;
                }
            })
            .filter(new Func1<List<ApiDataPoint>, Boolean>() {
                @Override
                public Boolean call(List<ApiDataPoint> apiDataPoints) {
                    boolean unfiltered = apiDataPoints.isEmpty();
                    if (unfiltered) {
                        Timber.d("filtered");
                    } else {
                        Timber.d("not filtered");
                    }
                    return unfiltered;
                }
            }).map(new Func1<List<ApiDataPoint>, Integer>() {
                @Override
                public Integer call(List<ApiDataPoint> apiDataPoints) {
                    Timber.d("Finished polling server");
                    return 0;
                }
            });
}

private Observable<List<ApiDataPoint>> loadAndSave(final String baseUrl, final String apiKey,
        final long surveyGroupId, final List<ApiDataPoint> lastBatch) {
    return loadNewDataPoints(baseUrl, apiKey, surveyGroupId)
            .concatMap(new Func1<ApiLocaleResult, Observable<List<ApiDataPoint>>>() {
                @Override
                public Observable<List<ApiDataPoint>> call(ApiLocaleResult apiLocaleResult) {
                    return saveToDataBase(apiLocaleResult, lastBatch);
                }
            });
}


private Observable<ApiLocaleResult> loadNewDataPoints(final String baseUrl, final String apiKey,
        final long surveyGroupId) {
    Timber.d("loadNewDataPoints");

    return Observable.just(true).concatMap(new Func1<Object, Observable<ApiLocaleResult>>() {
        @Override
        public Observable<ApiLocaleResult> call(Object o) {
            Timber.d("loadNewDataPoints call");
            return restApi
                    .loadNewDataPoints(baseUrl, apiKey, surveyGroupId,
                            getSyncedTime(surveyGroupId));
        }
    });
}

正如您所看到的,有趣的方法是loadNewDataPoint s,我希望在没有更多数据点之前调用它。正如你所看到的那样Observable.just(true).concatMap是一个黑客,因为如果我删除这个concat映射,restApi.loadNewDataPoints(....)不会被调用,虽然在日志中我可以看到api确实被调用但是具有相同的旧参数和当然它返回与第一次相同的结果,因此同步停止,saveToDataBase被称为罚款。随着我的黑客它的工作,但我想了解为什么它不起作用的另一种方式,如果有更好的方法来做到这一点。非常感谢!

1 个答案:

答案 0 :(得分:1)

所以,我已经编写了这种API(它被称为Keyset Pagination)并且针对它们实现了Rx客户端。

这是BehaviorSubjects有用的案例之一:

S initialState = null;
BehaviorProcessor<T> subject = BehaviorProcessor.createDefault(initialState);
return subject
  .flatMap(state -> getNextElements(state).singleOrError().toFlowable(), Pair::of, 1)
  .serialize()
  .flatMap(stateValuePair -> {
      S state = stateValuePair.getLeft();
      R retrievedValue = stateValuePair.getRight();
      if(isEmpty(retrievedValue)) {
         subject.onComplete();
         return Flowable.empty();
      } else {
         subject.onNext(getNextState(state, retrievedValue));
         return Flowable.just(retrievedValue);
      }
    }
   .doOnUnsubscribe(subject::onCompleted)
   .map(value -> ...)

下面

  • getNextElement根据状态执行网络呼叫,并返回具有单个值的反应流
  • isEmpty确定返回的值是否为空,表示元素结尾
  • getNextState将传入状态与检索到的值相结合,以确定getNextElement的下一个状态。

如果发生错误(它将被传播),并且在结束前取消订阅(查询将被终止),它将正常工作。

当然,在您的具体情况下,这些不需要是单独的方法或复杂类型。