PagedListAdapter переходит к началу списка при получении нового PagedList - PullRequest
0 голосов
/ 30 июня 2018

Я использую Библиотека подкачки для загрузки данных из сети, используя ItemKeyedDataSource. После извлечения элементов пользователь может их редактировать, эти обновления выполняются внутри кеша Memory (база данных, такая как Room, не используется).

Теперь, так как само PagedList не может быть обновлено (обсуждается здесь ), я должен воссоздать PagedList и передать его в PagedListAdapter.

Само обновление не проблема, но после обновления recyclerView новым PagedList список переходит к началу списка, уничтожая предыдущую позицию прокрутки. Есть ли способ обновить PagedList, сохраняя положение прокрутки (например, как он работает с Room )?

Источник данных реализован следующим образом:

public class MentionKeyedDataSource extends ItemKeyedDataSource<Long, Mention> {

    private Repository repository;
    ...
    private List<Mention> cachedItems;

    public MentionKeyedDataSource(Repository repository, ..., List<Mention> cachedItems){
        super();

        this.repository = repository;
        this.teamId = teamId;
        this.inboxId = inboxId;
        this.filter = filter;
        this.cachedItems = new ArrayList<>(cachedItems);
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Long> params, final @NonNull ItemKeyedDataSource.LoadInitialCallback<Mention> callback) {
        Observable.just(cachedItems)
                .filter(() -> return cachedItems != null && !cachedItems.isEmpty())
                .switchIfEmpty(repository.getItems(..., params.requestedLoadSize).map(...))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Long> params, final @NonNull ItemKeyedDataSource.LoadCallback<Mention> callback) {
        repository.getOlderItems(..., params.key, params.requestedLoadSize)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Long> params, final @NonNull ItemKeyedDataSource.LoadCallback<Mention> callback) {
        repository.getNewerItems(..., params.key, params.requestedLoadSize)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(response -> callback.onResult(response.data.list));
    }

    @NonNull
    @Override
    public Long getKey(@NonNull Mention item) {
        return item.id;
    }
}

PagedList , созданный так:

PagedList.Config config = new PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)
        .setInitialLoadSizeHint(preFetchedItems != null && !preFetchedItems.isEmpty()
                ? preFetchedItems.size()
                : PAGE_SIZE * 2
        ).build();

pagedMentionsList = new PagedList.Builder<>(new MentionKeyedDataSource(mRepository, team.id, inbox.id, mCurrentFilter, preFetchedItems)
        , config)
        .setFetchExecutor(ApplicationThreadPool.getBackgroundThreadExecutor())
        .setNotifyExecutor(ApplicationThreadPool.getUIThreadExecutor())
        .build();

PagedListAdapter создается следующим образом:

public class ItemAdapter extends PagedListAdapter<Item, ItemAdapter.ItemHolder> { //Adapter from google guide, Nothing special here.. }

mAdapter = new ItemAdapter(new DiffUtil.ItemCallback<Mention>() {
            @Override
            public boolean areItemsTheSame(Item oldItem, Item newItem) {
                return oldItem.id == newItem.id;
            }

            @Override
            public boolean areContentsTheSame(Item oldItem, Item newItem) {
                return oldItem.equals(newItem);
            }
        });

, и обновляется так:

mAdapter.submitList(pagedList);

Ответы [ 2 ]

0 голосов
/ 07 марта 2019

Вы должны использовать блокирующий вызов для вашего наблюдаемого. Если вы не отправите результат в том же потоке, что и loadInitial, loadAfter или loadBefore, происходит следующее: адаптер сначала вычисляет diff существующих элементов списка по пустому списку, а затем против вновь загруженных предметов. Так эффективно, как будто все элементы были удалены, а затем вставлены снова, поэтому список, кажется, переходит к началу.

0 голосов
/ 19 ноября 2018

Вы не используете androidx.paging.ItemKeyedDataSource.LoadInitialParams#requestedInitialKey в своей реализации loadInitial, и я думаю, что вы должны.

Я посмотрел на другую реализацию ItemKeyedDataSource, используемую автоматически сгенерированным кодом Room DAO: LimitOffsetDataSource. Его реализация loadInitial содержит ( Apache 2.0 лицензирован код следует):

// bound the size requested, based on known count final int firstLoadPosition = computeInitialLoadPosition(params, totalCount); final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);

... где эти функции что-то делают с params.requestedStartPosition, params.requestedLoadSize и params.pageSize.

Так что же не так?

Всякий раз, когда вы передаете новый PagedList, вы должны убедиться, что он содержит элементы, к которым пользователь в данный момент прокручивается. В противном случае ваш PagedListAdapter будет рассматривать это как удаление этих элементов. Затем, когда ваши элементы loadAfter или loadBefore загружают эти элементы, они будут обрабатывать их как последующую вставку этих элементов. Вы должны избегать этого удаления и вставки любых видимых предметов. Поскольку кажется, что вы прокручиваете вверх, возможно, вы случайно удаляете все элементы и вставляете их все.

Я думаю, что это работает при использовании Room с PagedLists:

  1. База данных обновлена.
  2. Наблюдатель из комнаты делает недействительным источник данных.
  3. Код PagedListAdapter определяет недействительность и использует фабрику для создания нового источника данных и вызывает loadInitial с params.requestedStartPosition, установленным в видимый элемент.
  4. Новый PagedList предоставляется PagedListAdapter, который запускает код проверки diff, чтобы увидеть, что на самом деле изменилось. Обычно ничего не меняется на то, что видно, но, возможно, элемент был вставлен, изменен или удален. Все, что находится за пределами начальной загрузки, считается удаленным - это не должно быть заметно в пользовательском интерфейсе.
  5. При прокрутке код PagedListAdapter может определить необходимость загрузки новых элементов и вызвать loadBefore или loadAfter.
  6. Когда они завершены, PagedListAdapter предоставляет полный новый PagedList, который запускает код проверки diff, чтобы увидеть, что на самом деле изменилось. Обычно - просто вставка.

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

Другие идеи

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

...