RecyclerView прокручивается вниз при обновлении данных с помощью Room и PagingListAdapter

Я создал пример проекта, чтобы показать проблему, с которой я столкнулся при использовании RecyclerView, Room и Paging, а именно: RecyclerView неожиданно прокручивается вниз при обновлении данных.

https://github.com/HappyPeng2x/RoomRecyclerViewExample

Приложение имеет базу данных Room, и я использую адаптер, производный от PagedListAdapter, для отображения его значений в RecyclerView.

Запрос рассматривается, как показано в приведенном ниже коде, поэтому каждое обновление таблицы будет отражаться адаптером.

   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);
                }
            });

Для тестирования я заполняю таблицу 1000 пар ключ/значение. Ключи начинаются с 1 и заканчиваются на 1000, и все значения инициируются как ИСХОДНЫЕ.

Я включаю в каждый отображаемый элемент кнопку-переключатель; нажатие на нее изменит значение с INITIAL на FINAL и наоборот.

При нажатии кнопки-переключателя на 155-м элементе отображаемое значение изменяется с INITIAL на FINAL без каких-либо проблем.

При выполнении той же операции с 243-м элементом нажатие кнопки приводит к прокрутке RecyclerView, что не ожидается.

Проблема повторяется каждый раз, когда нажимается кнопка вокруг этой позиции.

Я снял видео, чтобы можно было наблюдать за проблемой.

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

Я довольно долго боролся с этой проблемой, и мне немного стыдно, потому что это похоже на базовое использование архитектурных компонентов, поэтому я был бы очень рад получить любую помощь.


comment
Также см. мой комментарий к отчету об ошибке здесь: issuetracker.google.com/issues/123834703#. comment2 Мне было бы полезно, если бы у кого-то было мнение об этом.   -  person HappyPeng    schedule 09.02.2019


Ответы (3)


Эта проблема является ошибкой, которая была решена Google, и исправление должно быть включено в следующую версию Paging, чего, к сожалению, еще не произошло.

URL-адрес в системе отслеживания проблем: https://issuetracker.google.com/issues/123834703. - впрочем, так как нужно залогиниться, чтобы это увидеть, я скопирую сюда основные элементы.

Как объясняет разработчик, проблема заключается в том, что библиотека обычно инициирует первоначальную загрузку вокруг последнего доступного места, но когда заполнители отключены, эта логика обходится, поэтому загрузка происходит в месте. em> место последнего доступа. Поскольку это расположение часто является последним частично скрытым элементом, связанным с RecyclerView, это означает, что большинство элементов на экране отсутствуют при начальной загрузке и выгружаются только позже.

Выпуск, содержащий это исправление, еще не опубликован, вы должны использовать версию для разработки, но вы также можете добавить обходной путь, предложенный разработчиком для моего примера приложения, на странице https://github.com/HappyPeng2x .

1) Добавьте следующий класс в 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) Используйте обертку для dataSourceFactory:

    new LivePagedListBuilder<>
            (new RoomFactoryWrapper<>(mDB.getMyDao().getAllPaged()), plConfig)
person HappyPeng    schedule 22.09.2019
comment
Как насчет PageKeyedDataSource? - person masterwok; 28.10.2019

Хотя я не уверен, но это может вам помочь.

 PagedList.Config plConfig =
        new PagedList.Config.Builder()
        .setEnablePlaceholders(false)
        .setPrefetchDistance(30)
        .setPageSize(50).build();
person Anjan Debnath    schedule 05.02.2019
comment
Спасибо за ваш комментарий. Смысл вашего предложения в том, чтобы сказать, что я должен просто увеличить размер страницы и расстояние предварительной выборки? К сожалению, я попробовал несколько значений, и проблема осталась прежней... Но это происходит дальше по списку (несколько кратных размеру страницы). - person HappyPeng; 05.02.2019
comment
Я столкнулся с проблемой аналогичного типа, и увеличение размера страницы решило мою проблему. - person Anjan Debnath; 05.02.2019
comment
Я обновил свой код в соответствии с вашим предложением. К сожалению, хотя это снижает вероятность его возникновения, проблема остается той же. Запустите обновленное приложение, пролистайте так, чтобы элемент 52 оказался внизу экрана, затем щелкните элемент 45: будет наблюдаться такое же поведение. - person HappyPeng; 09.02.2019
comment
вы используете тег для каждого просмотра? - person Anjan Debnath; 12.02.2019
comment
к вашему сведению. вы можете взглянуть на это. Я сохранил пользовательскую разбивку по страницам, и вы можете просмотреть код github.com/anjandebnath/ArchitectureComponent/tree / - person Anjan Debnath; 12.02.2019

Я установил этот конфиг, и он отлично работает.

Основной элемент там setMaxSize(pageSize + 2 * prefetchDistance) в минимально возможном значении.

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

Я пробовал без него, и RecyclerView также визуально работал нормально, но PagedListAdapter неправильно связывал элементы.

person Микола Бихкало    schedule 13.11.2020