如何使用RxJava处理分页?

时间:2014-10-12 01:41:55

标签: rx-java

我正在考虑转换我的Android应用程序以使用Rxjava进行网络请求。我目前访问的网络服务类似于:

getUsersByKeyword(String query, int limit, int offset)

据我所知,Observables是一个“推”而不是一个“拉”界面。所以这就是我理解要解决的问题:

  • app注册服务,获取Observable查询
  • 结果会推送到应用
  • app处理结果
  • 当app想要更多结果时......?

这就是我遇到麻烦的地方。以前我只是问网络服务我想要什么,再次使用偏移量进行查询。但在这种情况下,这将涉及创建另一个Observable并订阅它,有点打败了这一点。

我应该如何在我的应用中处理分页? (这是一个Android应用程序,但我不认为这是相关的。)

4 个答案:

答案 0 :(得分:8)

这是硬摇滚!) 所以我们要求网络:
getUsersByKeyword(String query, int limit, int offset)
并且此请求返回例如
List< Result >
如果我们使用RetroFit进行网络连接请求: Observable< List< Result >> getUsersByKeyword(String query, int limit, int offset)
因此,我们希望从服务器获取所有Result
所以它看起来像这样

int page = 50;
int limit = page;
Observable
                .range(0, Integer.MAX_VALUE - 1)
                .concatMap(new Func1<Integer, Observable<List<Result>>>() {
                    @Override
                    public Observable<List<Result>> call(Integer integer) {
                        return getUsersByKeyword(query, integer * page, limit);
                    }
                })
                .takeWhile(new Func1<List<Result>, Boolean>() {
                    @Override
                    public Boolean call(List<Result> results) {
                        return !results.isEmpty();
                    }
                })
                .scan(new Func2< List<Result>, List<Result>, List<Result>>() {
                    @Override
                    public List<Result> call(List<Result> results, List< Result> results2) {
                        List<Result> list = new ArrayList<>();
                        list.addAll(results);
                        list.addAll(results2);
                        return list;
                    }
                })
                .last()
                .subscribe(new Subscriber<List<Result>>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(List<Results> results) {
                    }
                });

代码被测试了!

答案 1 :(得分:5)

所以,如果这是单向分页,那么你可以试试这种模式。此代码尚未运行或编译,但我已尝试过度注释以解释正在进行的操作。

private static final int LIMIT = 50;

// Given: Returns a stream of dummy event objects telling us when
// to grab the next page. This could be from a click or wherever.
Observable<NextPageEvent> getNextPageEvents();  

// Given:
// The search query keywords. Each emission here means a new top-level
// request;
Observable<String> queries;

queries.switchMap((query) -> getNextPageEvents()
        // Ignore 'next page' pokes when unable to take them.
        .onBackPressureDrop()
        // Seed with the first page.
        .startWith(new NextPageEvent())
        // Increment the page number on each request.
        .scan(0, (page, event) -> page + 1) 
        // Request the page from the server.
        .concatMap((page) -> getUsersByKeyword(query, LIMIT, LIMIT * page)
                // Unroll Observable<List<User> into Observable<User>
                .concatMap((userList) -> Observable.from(userList))
                .retryWhen(/** Insert your favorite retry logic here. */))
        // Only process new page requests sequentially.
        .scheduleOn(Schedulers.trampoline())
        // Trampoline schedules on the 'current thread', so we make sure that's
        // a background IO thread.
        .scheduleOn(Schedulers.io());

那应该让&#39;下一页活动&#39;信号每次触发下一页数据的加载,如果遇到加载错误的页面,则不跳页。如果收到新的搜索查询,它也会在顶层完全重新启动。如果我(或其他人?)有时间,我想检查我对蹦床和背压的假设,并确保它阻止任何人在加载时过早地获取下一页的尝试。

答案 2 :(得分:2)

我已经这样做了,实际上并没有那么难。

该方法是对firstRequestsObservable中的每个第一个请求(偏移0)进行建模。为了方便起见,您可以将其设置为PublishSubject,您可以在其中调用onNext()来提供下一个请求,但有更聪明的非主题方式(例如,如果在单击按钮时完成请求) ,那么requestObservable是通过一些运算符映射的clickObservable。)

firstRequestsObservable到位后,您可以通过从responseObservable进行flatMapping等来firstRequestsObservable进行服务调用。

现在来了解诀窍:创建另一个名为subsequentRequestsObservable的可观察对象,它从responseObservable映射,递增偏移量(为此,最好在响应数据中包含原始偏移量)请求)。引入此可观察对象后,您现在必须更改responseObservable的定义,以便它还取决于subsequentRequestsObservable。然后你得到一个像这样的循环依赖:

firstRequestsObservable - &gt; responseObservable - &gt; subsequentRequestsObservable - &gt; responseObservable - &gt; subsequentRequestsObservable - &gt; ...

要打破此循环,您可能希望在filter的定义中包含subsequentRequestsObservable运算符,过滤掉偏移量将超过“总”限制的情况。循环依赖还意味着您需要使其中一个成为Subject,否则将无法声明observable。我建议将responseObservable作为主题。

所以,总而言之,首先将responseObservable初始化为Subject,然后声明firstRequestsObservable,然后声明laterRequestsObservable作为通过一些运算符传递responseObservable的结果。然后可以使用onNext“输入”responseObservable。

答案 3 :(得分:0)

要明确的是,我认为您的问题更多的是如何在您的Android应用程序中应用RxJava而不是在后端(虽然它可以应用但不像前端那样典型)。 我不太确定这个例子是否是使用反应函数编程(RFP)模型的典型用例,除了干净的代码结构。

下面的每个流都是一个Observable。就个人而言,我想将其视为流,因为它很容易推理事件。 分页流程可以用6个流表示:

firstPageStream -f-------------------> // this is actually to produce very first event to obtain the first page result. The event can come from a touch in one of the screen that navigates to this list screen.
nextPageStream  -----n-------n-------> // this is the source of events coming from Next button 'touch' actions
prevPageStream  ---------p-------p---> // this is the source of events coming from Previous button 'touch' actions
requestStream   -r---r---r---r---r---> // this is to consume the signal from 3 streams above and spawn events (r) which create a query and pagination details i.e.: offset and limit
responseStream  -R---R---R---R---R---> // this is to take each r and invoke your web service getUsersByKeyword() then spawn the response (R)

上面的流可以用下面的伪代码(JS风格)表示(翻译成RxJava或其他语言相当容易)

firstPageStream = Observable.just(0); // offset=0
nextPageStream = Observable.fromEvent(nextButton, 'touch')
                           .map(function() {
                              offset = offset + limit;
                              return offset;
                            });
prevPageStream = Observable.fromEvent(prevButton, 'touch')
                           .map(function() {
                              offset = offset - limit; // TODO some check here
                              return offset;
                            });
requestStream = Observable.merge(firstPageStream, nextPageStream, prevPageStream)
                            .map(function(offsetValue) {
                               return {offset : offsetValue, limit: limit};
                            });
responseStream = requestStream.flatMap(function(pagination) {
                                return webservice.getUsersByKeyword(query, pagination.offset, pagination.limit); // assume this is async response
                              });
responseStream.subscribe(function(result) {
  // use result to render the display
});

PS1:我还没有测试过上面的代码。我正在学习RFP,所以我只是试着通过写下来以反应的方式思考。欢迎任何建议。

PS2:我对https://gist.github.com/staltz/868e7e9bc2a7b8c1f754对解释反应流的方式影响很大。