PageKeyedDataSource loadAfter doesnt get fire
Asked Answered
D

3

9

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>
Distillate answered 16/7, 2018 at 3:30 Comment(9)
where do you use a custom PagedListAdapter? tried some simple DataSources from dozens of tutorials available on the net?Nosology
@Nosology I have other demo projects which run PagedListAdapter. those are working but the problem is in my application.Distillate
i still cant see how/where you are using your ReportListAdapter - what is you Activity / Fragment like?Nosology
@Nosology its fragment. updated sample.Distillate
setup a breakpoint in PagedListAdapter#getItem method and see why PagedList#loadAround does not work as expectedNosology
@Nosology it works thanks.Distillate
@Nosology bitbucket.org/falconro/androidx-paging-library-demo-java/src/…Distillate
@Nosology if I disable that line adapter stop adding more itemsDistillate
Let us continue this discussion in chat.Distillate
D
12

In the above code as you can see I didn't have called getitem under onBindViewHolder but once after I requested for getItem(itemPostion) it all started working has it supposed to be

Demo Project:

androidx-paging-library-demo-java

Distillate answered 16/7, 2018 at 10:1 Comment(2)
This is very odd behavior and I couldn't find any documentation hinting towards it from Google. Sure am glad you pointed it outDialyser
I had a similar issue, and managed to find the problem because I realised onBindViewHolder wasn't getting called. Thanks!Cowberry
I
4

Apart from not calling getItem in onBindViewHolder, Not setting page size in config can also cause this problem, make sure you called: setPageSize(your_page_size) like this:

PagedList.Config.Builder()
            .setEnablePlaceholders(false)
            .setPageSize(20)
            .build() 
Idiocy answered 14/10, 2019 at 5:14 Comment(0)
Z
3

I have figured it out. The main problem is the Adapter extends PagedListAdapter:

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.textView.text = "position $position"
}

I use above code to make every item different, and method loadAfter is not being invoked. I read the source code in AsyncPagedListDiffer

   @SuppressWarnings("WeakerAccess")
    @Nullable
    public T getItem(int index) {
        if (mPagedList == null) {
            if (mSnapshot == null) {
                throw new IndexOutOfBoundsException(
                        "Item count is zero, getItem() call is invalid");
            } else {
                return mSnapshot.get(index);
            }
        }

        mPagedList.loadAround(index);
        return mPagedList.get(index);
    }

loadAfter is being invoked here. so we must use getItem in onBindViewHolder

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.textView.text = getItem(position)?.name
    }

Just work!

Zoller answered 21/11, 2019 at 8:52 Comment(2)
Its working fine if I am using getItem() from AsyncPagedListDiffer.Allochthonous
Thanks, I was using currentList.get(position). for getting item, getItem helped here.Wrapper

© 2022 - 2024 — McMap. All rights reserved.