无数字ID场景下Android Paging Library适配问题求助
我在调用旅游类API获取国家列表时,遇到了一个分页问题:接口返回的每个条目只有字符串类型的id(比如"id": "Benin"),没有数字ID,这导致我使用Android Paging Library的ItemKeyedDataSource时卡壳了——getKey()方法不知道该返回什么,而像GitHub API那种带数字ID的接口适配起来就很顺畅。
API响应示例
{ "results": [ { "name": "Benin", "country_id": "Benin", "snippet": "A safe and relatively easy country for travellers to visit; birthplace of the Voodoo religion and former home of the Kingdom of Dahomey.", "parent_id": null, "score": 3.98127216481287, "id": "Benin" } ], "estimated_total": 30845, "more": true }
问题代码
ItemKeyedCountryDataSource.java
public class ItemKeyedCountryDataSource extends ItemKeyedDataSource<Integer, CountryResult> { private EndpointHelper endpointHelper; ItemKeyedCountryDataSource() { endpointHelper = EndpointHelper.getInstance(); } @Override public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull final LoadInitialCallback<CountryResult> callback) { final List<CountryResult> countryResultList = new ArrayList<>(); endpointHelper.getCountryList(0) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .retry(1) .onErrorResumeNext(new Func1<Throwable, Observable<? extends CountryWrapper>>() { @Override public Observable<? extends CountryWrapper> call(Throwable throwable) { return Observable.error(throwable); } }) .subscribe(new Subscriber<CountryWrapper>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(CountryWrapper countryWrapper) { if (countryWrapper.getResults().size() > 0) { countryResultList.addAll(countryWrapper.getResults()); callback.onResult(countryResultList); } } }); } @Override public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull final LoadCallback<CountryResult> callback) { final List<CountryResult> countryResultList = new ArrayList<>(); endpointHelper.getCountryList(params.requestedLoadSize) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .retry(1) .onErrorResumeNext(new Func1<Throwable, Observable<? extends CountryWrapper>>() { @Override public Observable<? extends CountryWrapper> call(Throwable throwable) { return Observable.error(throwable); } }) .subscribe(new Subscriber<CountryWrapper>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(CountryWrapper countryWrapper) { if (countryWrapper.getResults().size() > 0) { countryResultList.addAll(countryWrapper.getResults()); callback.onResult(countryResultList); } } }); } @Override public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<CountryResult> callback) { //Do nothing } @NonNull @Override public Integer getKey(@NonNull CountryResult item) { return ?; // what do I return here? } }
ViewModel.java
public class SearchableActivityViewModel extends ViewModel { private LiveData<PagedList<CountryResult>> countryResult; SearchableActivityViewModel() { Executor executor = Executors.newFixedThreadPool(5); CountryResultDataSourceFactory countryResultDataSourceFactory = new CountryResultDataSourceFactory(); PagedList.Config config = new PagedList.Config.Builder() .setEnablePlaceholders(false) .setInitialLoadSizeHint(20) //first load .setPageSize(21) .build(); //noinspection unchecked countryResult = new LivePagedListBuilder<String, CountryResult>(countryResultDataSourceFactory, config) .setFetchExecutor(executor) .build(); } public LiveData<PagedList<CountryResult>> getCountryResult(){ return countryResult; } @Override protected void onCleared() { super.onCleared(); } }
解决方案
针对这个问题,我们有两种处理思路,取决于你的API支持哪种分页方式:
方案一:如果API支持基于字符串ID的分页(比如after参数)
这种情况下,我们可以继续使用ItemKeyedDataSource,只需要把泛型类型从Integer改成String即可:
修改数据源泛型
把ItemKeyedDataSource<Integer, CountryResult>改为ItemKeyedDataSource<String, CountryResult>。实现
getKey()方法
直接返回条目的字符串ID:@NonNull @Override public String getKey(@NonNull CountryResult item) { return item.getId(); // 返回字符串类型的ID作为分页键 }修正分页请求逻辑
原来的loadAfter方法里你错误地传递了params.requestedLoadSize,实际上应该用params.key(上一页最后一个条目的ID)作为分页参数传给API:@Override public void loadAfter(@NonNull LoadParams<String> params, @NonNull final LoadCallback<CountryResult> callback) { final List<CountryResult> countryResultList = new ArrayList<>(); // 传递上一页最后一个item的ID,请求下一页数据 endpointHelper.getCountryListAfter(params.key) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .retry(1) .onErrorResumeNext(throwable -> Observable.error(throwable)) .subscribe(countryWrapper -> { if (!countryWrapper.getResults().isEmpty()) { countryResultList.addAll(countryWrapper.getResults()); callback.onResult(countryResultList); } }); }同时需要在
EndpointHelper中新增getCountryListAfter(String lastId)方法,构建符合API要求的分页请求。
方案二:如果API只支持offset/limit分页
如果你的API只能通过offset(偏移量)和limit(每页数量)来分页,那更适合使用PageKeyedDataSource,它基于页的键(比如offset数值)来处理分页:
创建
PageKeyedCountryDataSourcepublic class PageKeyedCountryDataSource extends PageKeyedDataSource<Integer, CountryResult> { private EndpointHelper endpointHelper; PageKeyedCountryDataSource() { endpointHelper = EndpointHelper.getInstance(); } @Override public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, CountryResult> callback) { // 初始加载,offset=0 endpointHelper.getCountryList(0, params.requestedLoadSize) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .retry(1) .subscribe(countryWrapper -> { if (!countryWrapper.getResults().isEmpty()) { // 下一页的offset = 当前offset + 加载数量 callback.onResult(countryWrapper.getResults(), null, params.requestedLoadSize); } }, Throwable::printStackTrace); } @Override public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, CountryResult> callback) { // 不需要向前加载,留空即可 } @Override public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, CountryResult> callback) { // params.key是上一页的offset,计算下一页offset int nextOffset = params.key + params.requestedLoadSize; endpointHelper.getCountryList(nextOffset, params.requestedLoadSize) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .retry(1) .subscribe(countryWrapper -> { if (!countryWrapper.getResults().isEmpty()) { // 判断是否还有更多数据 Boolean hasMore = nextOffset < countryWrapper.getEstimated_total(); callback.onResult(countryWrapper.getResults(), hasMore ? nextOffset : null); } }, Throwable::printStackTrace); } }更新ViewModel中的数据源工厂
确保你的CountryResultDataSourceFactory返回PageKeyedCountryDataSource,同时修改LivePagedListBuilder的泛型:countryResult = new LivePagedListBuilder<Integer, CountryResult>(countryResultDataSourceFactory, config) .setFetchExecutor(executor) .build();
总结
- 如果API支持基于条目字符串ID的分页,优先用
ItemKeyedDataSource<String, CountryResult>; - 如果API只支持offset分页,切换到
PageKeyedDataSource<Integer, CountryResult>更合适。
内容的提问来源于stack exchange,提问作者nuhkoca

