In my fragment, I integrated android jetpack paging library and the data source I using PageKeyedDataSource with retrofit API callback.
the code runs as excepted and loads the data to recyclerview but after I scroll to the bottom, it supposed to load more data by firing loadAfter function in data source class but it didn't
I also switched to ItemKeyedDataSource still it fails to execute
is my code wrong or the plugin have an issue! but in some demo apps which I found in GitHub works perfectly I followed there code. please if anyone had this problem and fixed let me know Edit: Using AndroidX
public class ReportsDataSource extends PageKeyedDataSource<Integer, ReportItemModel> {
MutableLiveData<NetworkState> networkState = new MutableLiveData<>();
MutableLiveData<NetworkState> initialLoad = new MutableLiveData<>();
private UserService getUserService;
private List<Call<?>> compositeDisposable;
private CompositeDisposable compositeDisposableData;
private SchedulerProvider schedulerProvider;
private Completable retryCompletable = null;
public ReportsDataSource(UserService getUserService, List<Call<?>> compositeDisposable, CompositeDisposable compositeDisposableData, SchedulerProvider schedulerProvider) {
this.getUserService = getUserService;
this.compositeDisposable = compositeDisposable;
this.compositeDisposableData = compositeDisposableData;
this.schedulerProvider = schedulerProvider;
}
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, ReportItemModel> callback) {
networkState.postValue(NetworkState.LOADING);
initialLoad.postValue(NetworkState.LOADING);
loadPage(1, new callback() {
@Override
public void get(ReportListModel list) {
setRetry(null);
networkState.postValue(NetworkState.LOADED);
initialLoad.postValue(NetworkState.LOADED);
callback.onResult(list.data.items, 1, list.data.next);
}
@Override
public void failure(Throwable t) {
setRetry(() -> loadInitial(params, callback));
NetworkState error = NetworkState.error(t.getMessage());
networkState.postValue(error);
initialLoad.postValue(error);
}
});
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, ReportItemModel> callback) {
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, ReportItemModel> callback) {
networkState.postValue(NetworkState.LOADING);
loadPage(params.key, new callback() {
@Override
public void get(ReportListModel list) {
setRetry(null);
networkState.postValue(NetworkState.LOADED);
callback.onResult(list.data.items, list.data.next != params.key ? null : list.data.next);
}
@Override
public void failure(Throwable t) {
setRetry(() -> loadAfter(params, callback));
networkState.postValue(NetworkState.error(t.getMessage()));
}
});
}
public static void log(String msg) {
boolean threadMain = Looper.getMainLooper().getThread() == Thread.currentThread();
Timber.tag("Thread_finder_" + (threadMain ? "ui" : "none")).d(Thread.currentThread().getId() + " " + msg);
}
private void loadPage(int i, callback callback) {
log("loadPage");
Call<ReportListModel> call = getUserService.getReportsList(i);
compositeDisposable.add(call);
try {
Response<ReportListModel> response = call.execute();
log("onResponse");
if (RetrofitHelper.isSuccessful(response)) {
callback.get(response.body());
} else {
callback.failure(new Throwable("Model verification is failed"));
}
} catch (IOException e) {
callback.failure(e);
//e.printStackTrace();
}
}
public void retry() {
if (retryCompletable != null) {
compositeDisposableData.add(retryCompletable.subscribeOn(schedulerProvider.io()).observeOn(schedulerProvider.ui()).subscribe(() -> {
}, Timber::d));
}
}
private void setRetry(Action action) {
if (action == null) {
this.retryCompletable = null;
} else {
this.retryCompletable = Completable.fromAction(action);
}
}
@NonNull
public MutableLiveData<NetworkState> getNetworkState() {
return networkState;
}
@NonNull
public MutableLiveData<NetworkState> getInitialLoad() {
return initialLoad;
}
public interface callback {
void get(ReportListModel list);
void failure(Throwable t);
}
}
ViewModel
public class ReportsListViewModel extends ViewModel {
private static final int PAGE_SIZE = 10;
private Executor executor = Executors.newFixedThreadPool(5);
public LiveData<PagedList<ReportItemModel>> list;
private List<Call<?>> compositeDisposable = new ArrayList<>();
private CompositeDisposable compositeDisposableData = new CompositeDisposable();
private ReportsDataSourceFactory sourceFactory;
public final ObservableBoolean isErrorMessageVisible;
public final ObservableBoolean isRetryButtonVisible;
public final ObservableBoolean isLoadingProgressBarVisible;
public final ObservableBoolean isSwipeRefreshLayoutEnable;
public final ObservableField<String> errorMessage;
public ReportsListViewModel(UserService userService, SchedulerProvider schedulerProvider) {
sourceFactory = new ReportsDataSourceFactory(userService, compositeDisposable, compositeDisposableData, schedulerProvider);
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setEnablePlaceholders(false)
.build();
list = new LivePagedListBuilder<>(sourceFactory, config).build();
isErrorMessageVisible = new ObservableBoolean(false);
errorMessage = new ObservableField<>("");
isRetryButtonVisible = new ObservableBoolean(false);
isLoadingProgressBarVisible = new ObservableBoolean(true);
isSwipeRefreshLayoutEnable = new ObservableBoolean(true);
}
@Override
protected void onCleared() {
super.onCleared();
RetrofitStatic.clearRetrofitList(compositeDisposable);
compositeDisposableData.clear();
}
public void retry() {
sourceFactory.getDataSourceLiveData().getValue().retry();
}
public void refresh() {
sourceFactory.getDataSourceLiveData().getValue().invalidate();
}
public LiveData<NetworkState> getNetworkState() {
return Transformations.switchMap(sourceFactory.getDataSourceLiveData(), ReportsDataSource::getNetworkState);
}
public LiveData<NetworkState> getRefreshState() {
return Transformations.switchMap(sourceFactory.getDataSourceLiveData(), ReportsDataSource::getInitialLoad);
}
public void setInitialLoadingState(NetworkState networkState) {
isErrorMessageVisible.set((networkState.getMessage() != null));
errorMessage.set(networkState.getMessage());
isRetryButtonVisible.set(networkState.getStatus() == NetworkStateStatus.FAILED);
isLoadingProgressBarVisible.set(networkState.getStatus() == NetworkStateStatus.RUNNING);
isSwipeRefreshLayoutEnable.set(networkState.getStatus() == NetworkStateStatus.SUCCESS);
}
}
PageListAdapter
public class ReportListAdapter extends PagedListAdapter<ReportItemModel, RecyclerView.ViewHolder> {
public static final DiffUtil.ItemCallback<ReportItemModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<ReportItemModel>() {
@Override
public boolean areItemsTheSame(@NonNull ReportItemModel oldItem, @NonNull ReportItemModel newItem) {
return oldItem.reportId == newItem.reportId;
}
@Override
public boolean areContentsTheSame(@NonNull ReportItemModel oldItem, @NonNull ReportItemModel newItem) {
return oldItem.equals(newItem);
}
};
private NetworkState networkState = null;
private RetryCallback retryCallback;
public ReportListAdapter(RetryCallback retryCallback) {
super(DIFF_CALLBACK);
this.retryCallback = retryCallback;
}
@Override
public int getItemViewType(int position) {
if (hasExtraRow() && position == getItemCount() - 1) {
return R.layout.item_network_state;
} else {
return R.layout.recycler_report_item;
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case R.layout.recycler_report_item:
default:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_report_item, parent, false);
return new ViewHolder(view);
case R.layout.item_network_state:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_network_state, parent, false);
return new NetWorkStateHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ViewHolder) {
bindView((ViewHolder) holder, position, holder.itemView.getContext());
} else if (holder instanceof NetWorkStateHolder) {
bindNetworkView((NetWorkStateHolder) holder, position, holder.itemView.getContext());
}
}
private void bindNetworkView(NetWorkStateHolder holder, int position, Context context) {
NetworkStateItemViewModel mNetworkStateItemViewModel = new NetworkStateItemViewModel(networkState, retryCallback);
if (holder.binding != null) {
holder.binding.setViewModel(mNetworkStateItemViewModel);
holder.binding.executePendingBindings();
}
}
@Override
public int getItemCount() {
return super.getItemCount() + (hasExtraRow() ? 1 : 0);
}
private void bindView(ViewHolder holder, int position, Context context) {
holder.binding.reportId.setText( "Report ID: "+position);
}
private boolean hasExtraRow() {
return networkState != null && networkState != NetworkState.LOADED;
}
public void setNetworkState(NetworkState newNetworkState) {
if (getCurrentList() != null) {
if (getCurrentList().size() != 0) {
NetworkState previousState = this.networkState;
boolean hadExtraRow = hasExtraRow();
this.networkState = newNetworkState;
boolean hasExtraRow = hasExtraRow();
if (hadExtraRow != hasExtraRow) {
if (hadExtraRow) {
notifyItemRemoved(super.getItemCount());
} else {
notifyItemInserted(super.getItemCount());
}
} else if (hasExtraRow && previousState != newNetworkState) {
notifyItemChanged(getItemCount() - 1);
}
}
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
private final RecyclerReportItemBinding binding;
public ViewHolder(@NonNull View itemView) {
super(itemView);
binding = DataBindingUtil.bind(itemView);
}
}
public class NetWorkStateHolder extends RecyclerView.ViewHolder {
private final ItemNetworkStateBinding binding;
private final NetworkStateItemViewModel mNetworkStateItemViewModel;
public NetWorkStateHolder(@NonNull View itemView) {
super(itemView);
mNetworkStateItemViewModel = new NetworkStateItemViewModel(networkState, retryCallback);
binding = DataBindingUtil.bind(itemView);
binding.setViewModel(mNetworkStateItemViewModel);
binding.executePendingBindings();
}
}
}
Fragment:
public class ReportsFragment extends ParentFragment implements RetryCallback {
private ReportsFragment.callback callback;
private FragmentReportsListBinding binding;
private ReportsListViewModel reportViewModel;
private ReportListAdapter adapter;
public void setup(callback callback) {
this.callback = callback;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_reports_list, container, false);
reportViewModel = ViewModelProviders.of(this, mViewModelFactory).get(ReportsListViewModel.class);
binding.setViewModel(reportViewModel);
binding.executePendingBindings();
initAdapter();
initSwipeToRefresh();
return binding.getRoot();
}
private void initSwipeToRefresh() {
reportViewModel.getRefreshState().observe(this, networkState -> {
if (adapter.getCurrentList() != null) {
if (adapter.getCurrentList().size() > 0) {
binding.usersSwipeRefreshLayout.setRefreshing(networkState != null && networkState.getStatus() == NetworkState.LOADING.getStatus());
} else {
setInitialLoadingState(networkState);
}
} else {
setInitialLoadingState(networkState);
}
});
}
private void setInitialLoadingState(NetworkState networkState) {
reportViewModel.setInitialLoadingState(networkState);
}
private void initAdapter() {
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
adapter = new ReportListAdapter(this);
binding.recycler.setLayoutManager(linearLayoutManager);
binding.recycler.setAdapter(adapter);
reportViewModel.list.observe(this, adapter::submitList);
reportViewModel.getNetworkState().observe(this, adapter::setNetworkState);
}
@Override
public void retry() {
reportViewModel.retry();
}
public interface callback {
}
}
XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="dasarahalli.portal.adapters.paging.reports.ReportsListViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/usersSwipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:enabled="@{viewModel.isSwipeRefreshLayoutEnable}"
app:onRefreshListener="@{viewModel::refresh}">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/errorMessageTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@{viewModel.errorMessage}"
android:visibility="@{viewModel.isErrorMessageVisible ? View.VISIBLE : View.GONE}" />
<ProgressBar
android:id="@+id/loadingProgressBar"
style="?android:attr/progressBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="@{viewModel.isLoadingProgressBarVisible ? View.VISIBLE : View.GONE}" />
<Button
android:id="@+id/retryLoadingButton"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:onClick="@{() -> viewModel.retry()}"
android:text="RETRY"
android:visibility="@{viewModel.isRetryButtonVisible ? View.VISIBLE : View.GONE}" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
PagedListAdapter
? tried some simpleDataSource
s from dozens of tutorials available on the net? – NosologyReportListAdapter
- what is youActivity
/Fragment
like? – NosologyPagedListAdapter#getItem
method and see whyPagedList#loadAround
does not work as expected – Nosology