我正在考虑转换我的Android应用程序以使用Rxjava进行网络请求。我目前访问的网络服务类似于:
getUsersByKeyword(String query, int limit, int offset)
据我所知,Observables是一个“推”而不是一个“拉”界面。所以这就是我理解要解决的问题:
这就是我遇到麻烦的地方。以前我只是问网络服务我想要什么,再次使用偏移量进行查询。但在这种情况下,这将涉及创建另一个Observable并订阅它,有点打败了这一点。
我应该如何在我的应用中处理分页? (这是一个Android应用程序,但我不认为这是相关的。)
答案 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对解释反应流的方式影响很大。