HappyPeng
HappyPeng

Reputation: 91

RecyclerView scrolls down on data update using Room and PagingListAdapter

I created an example project to show an issue I am been struggling with using RecyclerView, Room and Paging, which is that the RecyclerView is unexpectedly scrolling down on data updating.

https://github.com/HappyPeng2x/RoomRecyclerViewExample

The app has a Room database, and I am using an adapter derived from PagedListAdapter to display its values in a RecyclerView.

The query is observed as shown by the code below so that every update of the table will be reflected by the adapter.

   PagedList.Config plConfig =
            new PagedList.Config.Builder().setEnablePlaceholders(false)
            .setPrefetchDistance(10)
            .setPageSize(20).build();

    new LivePagedListBuilder<>
            (mDB.getMyDao().getAllPaged(), plConfig)
            .build()
            .observe(this, new Observer<PagedList<MyEntry>>() {
                @Override
                public void onChanged(PagedList<MyEntry> myList) {
                    adapter.submitList(myList);
                }
            });

For testing I populate the table with 1 000 key/value pairs. The keys start at 1 and end at 1 000, and the values are all initiated as INITIAL.

I include in each displayed element a toggle button; clicking on it will toggle the value from INITIAL to FINAL and reverse.

When pressing the toggle button at the 155th element, the displayed value changes from INITIAL to FINAL without any issue.

When doing the same operation at the 243rd element, pressing the button causes the RecyclerView to scroll down, which is not expected.

The issue repeats itself each time a button is pressed around this position.

I took a video capture so that the issue can be observed.

https://github.com/HappyPeng2x/RoomRecyclerViewExample/blob/master/videos/device-2019-02-02-105434.webm

I have been struggling quite a bit with this issue, and feel a bit ashamed because it seems like a basic use of Architecture Components, so I would be really happy to get any help.

Upvotes: 6

Views: 2416

Answers (3)

I set this config and it works great.

The main item there is setMaxSize(pageSize + 2 * prefetchDistance) in minimum possible value.

val config = PagedList.Config.Builder()
            .setEnablePlaceholders(true)
            .setMaxSize(pageSize + 2 * prefetchDistance)
            .setPrefetchDistance(prefetchDistance)
            .setPageSize(pageSize)
            .build()`

I tried without it and RecyclerView also worked visually fine but PagedListAdapter incorrectly bound elements.

Upvotes: 0

HappyPeng
HappyPeng

Reputation: 91

This issue is a bug, that has been solved by Google and the fix should be included in the next release of Paging, which has unfortunately not happened yet.

The URL in the issue tracker is https://issuetracker.google.com/issues/123834703 - however as it is necessary to be logged to see it I will copy the main elements here.

As the developer explains it, the problem is that the library typically triggers an initial load around the last accessed location, but when placeholders are disabled, this logic is bypassed so the load occurs at the last accessed location. Since this location is often the last partially-offscreen item bound by the RecyclerView, that means most of the items onscreen are missing from the initial load, and only paged in later.

No release has been published containing this fix yet, you have to use a development version, but you can also add a workaround which was suggested by the developer for my example app at https://github.com/HappyPeng2x/RoomRecyclerViewExample .

1) Add the following class to MainActivity.java:

static class RoomFactoryWrapper<T> extends DataSource.Factory<Integer, T> {
    final DataSource.Factory<Integer, T> m_wrappedFactory;

    RoomFactoryWrapper(@NonNull Factory<Integer, T> wrappedFactory) {
        m_wrappedFactory = wrappedFactory;
    }

    @NonNull
    @Override
    public DataSource<Integer, T> create() {
        return new DataSourceWrapper<>((PositionalDataSource<T>) m_wrappedFactory.create());
    }

    static class DataSourceWrapper<T> extends PositionalDataSource<T> {
        final PositionalDataSource<T> m_wrappedSource;

        DataSourceWrapper(PositionalDataSource<T> wrappedSource) {
            m_wrappedSource = wrappedSource;
        }

        @Override
        public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
            m_wrappedSource.addInvalidatedCallback(onInvalidatedCallback);
        }

        @Override
        public void removeInvalidatedCallback(
            @NonNull InvalidatedCallback onInvalidatedCallback) {
            m_wrappedSource.removeInvalidatedCallback(onInvalidatedCallback);
        }

        @Override
        public void invalidate() {
            m_wrappedSource.invalidate();
        }

        @Override
        public boolean isInvalid() {
            return m_wrappedSource.isInvalid();
        }

        @Override
        public void loadInitial(@NonNull LoadInitialParams params,
            @NonNull LoadInitialCallback<T> callback) {
            // Workaround for paging bug: https://issuetracker.google.com/issues/123834703
            // edit initial load position to start 1/2 load ahead of requested position
            int newStartPos = params.placeholdersEnabled
                ? params.requestedStartPosition
                : Math.max(0, params.requestedStartPosition - (params.requestedLoadSize / 2));
            m_wrappedSource.loadInitial(new LoadInitialParams(
                newStartPos,
                params.requestedLoadSize,
                params.pageSize,
                params.placeholdersEnabled
            ), callback);
        }

        @Override
        public void loadRange(@NonNull LoadRangeParams params,
            @NonNull LoadRangeCallback<T> callback) {
            m_wrappedSource.loadRange(params, callback);
        }
    }
}

2) Use the wrapper on the dataSourceFactory:

    new LivePagedListBuilder<>
            (new RoomFactoryWrapper<>(mDB.getMyDao().getAllPaged()), plConfig)

Upvotes: 2

Anjan Debnath
Anjan Debnath

Reputation: 120

Though I am not confident but this might be help you.

 PagedList.Config plConfig =
        new PagedList.Config.Builder()
        .setEnablePlaceholders(false)
        .setPrefetchDistance(30)
        .setPageSize(50).build();

Upvotes: 0

Related Questions