RecyclerView scrolls down on data update using Room and PagingListAdapter
E

3

6

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.

Everick answered 2/2, 2019 at 2:44 Comment(1)
Please also refer to my comment on my bug report here: issuetracker.google.com/issues/123834703#comment2 It would be helpful for me if anyone has an opinion about it.Everick
E
2

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)
Everick answered 22/9, 2019 at 10:26 Comment(1)
What about PageKeyedDataSource?Sanhedrin
B
0

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();
Billhead answered 5/2, 2019 at 10:4 Comment(5)
Thank you for your comment. Is the meaning of your suggestion to say I should just increase page size and prefetch distance? Unfortunately, I tried several values and the problem stays the same... But it happens farther down the list (several multiples of page size).Everick
I have faced similar type problem and increasing the page size solved my problem.Billhead
I updated my code according to your suggestion. Unfortunately, though it reduces the probability of its occurring, the issue is the same. Execute the updated app, croll so that item 52 is at the bottom of the screen then click on item 45: the same behavior will be observed.Everick
are you using tag for each view?Billhead
FYI. you can take a look on this. I have maintained a custom paging and you can go through the code github.com/anjandebnath/ArchitectureComponent/tree/…Billhead
J
0

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.

Jointless answered 13/11, 2020 at 7:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.