我正在尝试使用分页库+ RxJava 创建应用程序,一切正常,它从后端加载数据并将其插入到我的 Recyclerview 中,当我向下滚动 LoadAfter 正常加载下一页时,我的问题是当我向上滑动以刷新时使用 SwipeLayoutRefrech 的数据会使数据源无效,并且只调用 LoadInitial (仅加载 20 个第一项),而 LoadAfter 根本不调用,我花了很多时间在上面,但仍然没有解决方案。
这是我的数据源工厂:
public class GalleryPhotosDataSourceFactory extends DataSource.Factory<Integer, GalleryPhotosItems> {
private CompositeDisposable compositeDisposable;
private MutableLiveData<GalleryPhotosDataSource> DataSourceMutableLiveData = new MutableLiveData<>();
/**
* Create a DataSource.
* <p>
* The DataSource should invalidate itself if the snapshot is no longer valid. If a
* DataSource becomes invalid, the only way to query more data is to create a new DataSource
* from the Factory.
* <p>
* {@link } for example will construct a new PagedList and DataSource
* when the current DataSource is invalidated, and pass the new PagedList through the
* {@code LiveData<PagedList>} to observers.
*
* @return the new DataSource.
*/
public GalleryPhotosDataSourceFactory(CompositeDisposable compositeDisposable) {
this.compositeDisposable = compositeDisposable;
}
@Override
public DataSource<Integer, GalleryPhotosItems> create() {
GalleryPhotosDataSource galleryPhotosDataSource = new GalleryPhotosDataSource(compositeDisposable);
DataSourceMutableLiveData.postValue(galleryPhotosDataSource);
return galleryPhotosDataSource;
}
@NonNull
public MutableLiveData<GalleryPhotosDataSource> getDataSourceLiveData() {
return DataSourceMutableLiveData ;
}
}
我的类数据源用于从我的服务器加载数据:
public class GalleryPhotosDataSource extends ItemKeyedDataSource<Integer, GalleryPhotosItems> {
public static final String TAG = "GalleryPhotosDataSource";
private ApiService apiService;
private CompositeDisposable compositeDisposable;
private MutableLiveData<NetworkState> networkState = new MutableLiveData<>();
private MutableLiveData<NetworkState> initialLoad = new MutableLiveData<>();
/**
* Keep Completable reference for the retry event
*/
private Completable retryCompletable;
GalleryPhotosDataSource(CompositeDisposable compositeDisposable) {
this.apiService = Client.createAppService();
this.compositeDisposable = compositeDisposable;
}
public void retry() {
if (retryCompletable != null) {
compositeDisposable.add(retryCompletable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
}, throwable -> Timber.e(throwable.getMessage())));
}
}
/**
* Load initial data.
* <p>
* This method is called first to initialize a PagedList with data. If it's possible to count
* the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
* the callback via the three-parameter
* {@link LoadInitialCallback#onResult(List, int, int)}. This enables PagedLists
* presenting data from this source to display placeholders to represent unloaded items.
* <p>
* {@link LoadInitialParams#requestedInitialKey} and {@link LoadInitialParams#requestedLoadSize}
* are hints, not requirements, so they may be altered or ignored. Note that ignoring the
* {@code requestedInitialKey} can prevent subsequent PagedList/DataSource pairs from
* initializing at the same location. If your data source never invalidates (for example,
* loading from the network without the network ever signalling that old data must be reloaded),
* it's fine to ignore the {@code initialLoadKey} and always start from the beginning of the
* data set.
*
* @param params Parameters for initial load, including initial key and requested size.
* @param callback Callback that receives initial load data.
*/
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<GalleryPhotosItems> callback) {
// update network states.
// we also provide an initial load state to the listeners so that the UI can know when the
// very first list is loaded.
networkState.postValue(NetworkState.LOADING);
initialLoad.postValue(NetworkState.LOADING);
//get the initial users from the api
compositeDisposable.add(apiService.getPhotosGallery(1, 5, -1, params.requestedLoadSize).subscribe(galleryPhotosItemses -> {
// clear retry since last request succeeded
setRetry(null);
networkState.postValue(NetworkState.LOADED);
initialLoad.postValue(NetworkState.LOADED);
callback.onResult(galleryPhotosItemses);
},
throwable -> {
// keep a Completable for future retry
setRetry(() -> loadInitial(params, callback));
NetworkState error = NetworkState.error(throwable.getMessage());
// publish the error
networkState.postValue(error);
initialLoad.postValue(error);
}));
}
/**
* Load list data after the key specified in {@link LoadParams#key LoadParams.key}.
* <p>
* It's valid to return a different list size than the page size if it's easier, e.g. if your
* backend defines page sizes. It is generally safer to increase the number loaded than reduce.
* <p>
* Data may be passed synchronously during the loadAfter method, or deferred and called at a
* later time. Further loads going down will be blocked until the callback is called.
* <p>
* If data cannot be loaded (for example, if the request is invalid, or the data would be stale
* and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
* and prevent further loading.
*
* @param params Parameters for the load, including the key to load after, and requested size.
* @param callback Callback that receives loaded data.
*/
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<GalleryPhotosItems> callback) {
// set network value to loading.
networkState.postValue(NetworkState.LOADING);
//get the users from the api after id
compositeDisposable.add(apiService.getPhotosGallery(1, 5, params.key-1, params.requestedLoadSize).subscribe(galleryPhotosItemses -> {
// clear retry since last request succeeded
setRetry(null);
networkState.postValue(NetworkState.LOADED);
callback.onResult(galleryPhotosItemses);
},
throwable -> {
// keep a Completable for future retry photos_user/5/1534161047_1534160953579.jpg
setRetry(() -> loadAfter(params, callback));
// publish the error
networkState.postValue(NetworkState.error(throwable.getMessage()));
}));
}
/**
* Load list data before the key specified in {@link LoadParams#key LoadParams.key}.
* <p>
* It's valid to return a different list size than the page size if it's easier, e.g. if your
* backend defines page sizes. It is generally safer to increase the number loaded than reduce.
* <p>
* <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
* passed, so if you vary size, ensure that the last item is adjacent to the passed key.
* <p>
* Data may be passed synchronously during the loadBefore method, or deferred and called at a
* later time. Further loads going up will be blocked until the callback is called.
* <p>
* If data cannot be loaded (for example, if the request is invalid, or the data would be stale
* and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
* and prevent further loading.
*
* @param params Parameters for the load, including the key to load before, and requested size.
* @param callback Callback that receives loaded data.
*/
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<GalleryPhotosItems> callback) {
}
/**
* Return a key associated with the given item.
* <p>
* If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique
* integer ID, you would return {@code item.getID()} here. This key can then be passed to
* {@link #loadBefore(LoadParams, LoadCallback)} or
* {@link #loadAfter(LoadParams, LoadCallback)} to load additional items adjacent to the item
* passed to this function.
* <p>
* If your key is more complex, such as when you're sorting by name, then resolving collisions
* with integer ID, you'll need to return both. In such a case you would use a wrapper class,
* such as {@code Pair<String, Integer>} or, in Kotlin,
* {@code data class Key(val name: String, val id: Int)}
*
* @param item Item to get the key from.
* @return Key associated with given item.
*/
@NonNull
@Override
public Integer getKey(@NonNull GalleryPhotosItems item) {
return item.getPhoto_id();
}
@NonNull
public MutableLiveData<NetworkState> getNetworkState() {
return networkState;
}
@NonNull
public MutableLiveData<NetworkState> getInitialLoad() {
return initialLoad;
}
private void setRetry(final Action action) {
if (action == null) {
this.retryCompletable = null;
} else {
this.retryCompletable = Completable.fromAction(action);
}
}
}
我的视图模型:
public class GalleryPhotosViewModel extends ViewModel {
public LiveData<PagedList<GalleryPhotosItems>> photosList;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private static final int pageSize = 10;
private GalleryPhotosDataSourceFactory photosDataSourceFactory;
public GalleryPhotosViewModel() {
photosDataSourceFactory = new GalleryPhotosDataSourceFactory(compositeDisposable);
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(pageSize)
.setInitialLoadSizeHint(pageSize * 2)
.setEnablePlaceholders(true)
.build();
photosList = new LivePagedListBuilder<>(photosDataSourceFactory, config).build();
}
public void retry() {
photosDataSourceFactory.getDataSourceLiveData().getValue().retry();
}
public void refresh() {
photosDataSourceFactory.getDataSourceLiveData().getValue().invalidate();
}
public LiveData<NetworkState> getNetworkState() {
return Transformations.switchMap(photosDataSourceFactory.getDataSourceLiveData(), GalleryPhotosDataSource::getNetworkState);
}
public LiveData<NetworkState> getRefreshState() {
return Transformations.switchMap(photosDataSourceFactory.getDataSourceLiveData(), GalleryPhotosDataSource::getInitialLoad);
}
@Override
protected void onCleared() {
super.onCleared();
compositeDisposable.dispose();
}
}
最后是我的 initAdapter 和 SwipeRefreshLayout 片段:
public class AbonnesFragment extends Fragment implements RetryCallback{
@BindView(usersSwipeRefreshLayout)
SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.usersRecyclerView)
RecyclerView usersRecyclerView;
@BindView(R.id.errorMessageTextView)
TextView errorMessageTextView;
@BindView(R.id.retryLoadingButton)
Button retryLoadingButton;
@BindView(R.id.loadingProgressBar)
ProgressBar loadingProgressBar;
private GalleryPhotosViewModel viewModel;
private GalleryPhotosAdapter Adapter;
View rootView;
public AbonnesFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_photos, container, false);
ButterKnife.bind(this, rootView);
viewModel = ViewModelProviders.of(this).get(GalleryPhotosViewModel.class);
initAdapter();
initSwipeToRefresh();
return rootView;
}
private void initAdapter() {
GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 3);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
switch(Adapter.getItemViewType(position)){
case R.layout.gallery_photos_items:
return 1;
case R.layout.item_network_state:
return 3;
default:
return -1;
}
}
});
Adapter = new GalleryPhotosAdapter(this);
usersRecyclerView.setLayoutManager(gridLayoutManager);
usersRecyclerView.setAdapter(Adapter);
viewModel.photosList.observe(this, Adapter::submitList);
viewModel.getNetworkState().observe(this, Adapter::setNetworkState);
}
/**
* Init swipe to refresh and enable pull to refresh only when there are items in the adapter
*/
private void initSwipeToRefresh() {
viewModel.getRefreshState().observe(this, networkState -> {
if (networkState != null) {
if (Adapter.getCurrentList() != null) {
if (Adapter.getCurrentList().size() > 0) {
mSwipeRefreshLayout.setRefreshing(networkState.getStatus() == NetworkState.LOADING.getStatus());
} else {
setInitialLoadingState(networkState);
}
} else {
setInitialLoadingState(networkState);
}
}
});
mSwipeRefreshLayout.setOnRefreshListener(() -> viewModel.refresh());
}
/**
* Show the current network state for the first load when the user list
* in the adapter is empty and disable swipe to scroll at the first loading
*
* @param networkState the new network state
*/
private void setInitialLoadingState(NetworkState networkState) {
//error message
errorMessageTextView.setVisibility(networkState.getMessage() != null ? View.VISIBLE : View.GONE);
if (networkState.getMessage() != null) {
errorMessageTextView.setText(networkState.getMessage());
}
//loading and retry
retryLoadingButton.setVisibility(networkState.getStatus() == Status.FAILED ? View.VISIBLE : View.GONE);
loadingProgressBar.setVisibility(networkState.getStatus() == Status.RUNNING ? View.VISIBLE : View.GONE);
if (networkState.getStatus() == Status.SUCCESS){
mSwipeRefreshLayout.setEnabled(true);
} else {
mSwipeRefreshLayout.setEnabled(false);
}
}
@OnClick(R.id.retryLoadingButton)
void retryInitialLoading() {
viewModel.retry();
}
@Override
public void retry() {
viewModel.retry();
}
}
这似乎是一个已报告并已修复的错误https://issuetracker.google.com/issues/113122599
已在 2.1.0-alpha01 中修复
修复了极小的初始加载大小和未更改的数据将导致无法进一步加载的情况 b/113122599