Android pagination boundary callback clarification
M

1

6

I've integrated the pagination component in my app and it's working perfectly fine (almost). I've Database + network model. Database initially has some items which are consumed by LivePagedListBuilder. I observe this LiveData<PagedList<InboxEntity>> and ultimately feed the list to PagedListAdapter#submitList(PagedList<T> pagedList), something like :

LiveData<PagedList<Entity>> entities;
PagedList.Config pagedListConfig =
            (new PagedList.Config.Builder()).setEnablePlaceholders(true)
                .setPrefetchDistance(5)
                .setEnablePlaceholders(false)
                .setPageSize(10)
                .setInitialLoadSizeHint(10)
                .build();

entities = new LivePagedListBuilder<>(DAO.getItemList(), pagedListConfig)
entities.observe(this, new Observer<PagedList<Entity>>() {
            @Override
            public void onChanged(@Nullable PagedList<Entity> inboxEntities) {
                inboxAdapter.submitList(inboxEntities);
                isFirstLoad = false;
            }
        });

DAO#getItemList returns DataSource.Factory<Integer, Entity>.

I am listening for the boundary callback and trigger a network call when it reaches the end of the paged list. That call populates the database again.

There's one more thing. I've registered AdapterDataObserver on recycler view because if an item has been inserted at the beginning, I've to scroll to the top position:

RecyclerView.AdapterDataObserver adapterDataObserver = new RecyclerView.AdapterDataObserver() {
            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {

                if (positionStart == 0) {
                    layoutManager.scrollToPositionWithOffset(positionStart, 0);
                }
            }
        };

I am facing a problem in this model :

After making the network call, database is populated again and onChanged function is called with a new PagedList<Entity>. Now, does this paged list contains only the new items. I've confirmed this.

But onItemRangeInserted method is called with positionStart as 0 too, which suggests that items are being inserted at the beginning. But they are not. They are being inserted at the end, confirmed with stetho db inspector.

Then why is the onItemRangeInserted being called with positionStart as 0? This is making it difficult for me to distinguish when a fresh item is inserted at the beginning of the adapter and when items are inserted at the end.

Edit:

value of itemCount is 10 which is my page size.

In DiffCallback, I just compare the primary key column of the two entities in areItemsTheSame function.

Mogador answered 8/5, 2018 at 10:45 Comment(4)
I don't remember exactly. I found this code in my repo : if (positionStart == 0 && itemCount == 1) { layoutManager.scrollToPositionWithOffset(0, 0); }Mogador
did you try this? ^ @FrancisMogador
@Yashasvi yes, I did. But still there are more than one item added to top sometimesDimarco
I can confirm this happens to me alsoMixologist
M
0

This is how I'm doing it, not ideal but it works. I'm using a recycler view with reverse set to true as it's a chat app so you'll have to adjust for your use case.

In your fragment create a Timer field:

private Timer mTimer;

Add a scroll state listener that sets a "latest row id" that you can compare later.

 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

                if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                    //Get the latest id when we started scrolling
                    AppExecutors.getInstance().databaseIO().execute(() ->
                            mLatestMessageId = mHomeViewModel.getMessageViewModel().getLatestMessageId(mOurUsername, mChatname));
                }
            }});

Use the AdapterDataObserver to kick off a new timer when the insert is called.

 private RecyclerView.AdapterDataObserver mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {

        @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {

        if (mTimer != null) {
            mTimer.cancel();
        }


        mTimer = new Timer();
        mTimer.schedule(new CheckNewMessageTimer(), 100);

    }
};

Here's the timer class, if new items have been inserted the "latest row id" will have incremented in the database. I'm only scrolling to the bottom if I'm already at the bottom, otherwise I just change the color of a "scroll to bottom" FAB that I have.

  class CheckNewMessageTimer extends TimerTask {
        @Override
        public void run() {
            if (newItemsAdded()) {
                int firstItemPosition = mLinearLayoutManager.findFirstVisibleItemPosition();

                boolean atBottom = firstItemPosition == 1;
                SurespotLog.d(TAG, "CheckNewMessageTimer, firstItemPosition: %d, atBottom: %b", firstItemPosition, atBottom);

                AppExecutors.getInstance().mainThread().execute(() -> {
                    if (atBottom) {
                        SurespotLog.d(TAG, "CheckNewMessageTimer, scrolling to bottom");
                        mListView.scrollToPosition(0);

                    }
                    else {
                        if (mFab != null) {
                            SurespotLog.d(TAG, "CheckNewMessageTimer, new message received but we're not scrolled to the bottom, turn the scroll button blue");
                            mFab.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.surespotBlue)));
                        }
                    }
                });
            }
        }

        private boolean newItemsAdded() {
            int dbLatestMessageID = mHomeViewModel.getMessageViewModel().getLatestMessageId(mOurUsername, mChatname);

            if (mLatestMessageId > 0 && dbLatestMessageID > mLatestMessageId) {
                mLatestMessageId = dbLatestMessageID;
                return true;
            }

            return false;
        }
    }
Militarize answered 17/6, 2019 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.