Обновить элементы списка в PagingLibrary без использования комнаты (только сеть)

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

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

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

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

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

P.S: Если есть лучший способ обновить элементы списка без использования Room, поделитесь.


comment
Сможете ли вы реализовать приведенное ниже решение? Работает ? не могли бы вы обновить образец кода для справки.   -  person android.fryo    schedule 25.02.2020
comment
Вы реализовали это, поделитесь здесь, если у вас есть решение   -  person Mahdi Javaheri    schedule 04.07.2020


Ответы (2)


Все, что вам нужно сделать, - это аннулировать текущий DataSource каждый раз, когда вы обновляете свои данные.

Что бы я сделал:

  • переместите сетевую логику из MentionKeyedDataSource в новый класс, расширяющий PagedList.BoundaryCallback, и установите его при создании вашего PagedList.

  • переместите все свои данные в какой-нибудь DataProvider, который содержит все загруженные вами данные и ссылается на DataSource. Каждый раз, когда данные обновляются в DataProvider, делать недействительным текущий DataSource

Что-то вроде этого может быть

    val dataProvider = PagedDataProvider()
    val dataSourceFactory = InMemoryDataSourceFactory(dataProvider = dataProvider)

куда

class PagedDataProvider : DataProvider<Int, DataRow> {

    private val dataCache = mutableMapOf<Int, List<DataRow>>()
    override val sourceLiveData = MutableLiveData<DataSource<Int, DataRow>>()

    //implement data add/remove/update here
    //and after each modification call
    //sourceLiveData.value?.invalidate()
}

а также

class InMemoryDataSourceFactory<Key, Value>(
    private val dataProvider: DataProvider<Key, Value>
) : DataSource.Factory<Key, Value>() {

    override fun create(): DataSource<Key, Value> {
        val source = InMemoryDataSource(dataProvider = dataProvider)
        dataProvider.sourceLiveData.postValue(source)
        return source
    }
}

Этот подход очень похож на то, что делает Room - каждый раз, когда строка таблицы обновляется - текущая DataSource становится недействительной, а DataSourceFactory создает новый источник данных.

person Martynas Jurkus    schedule 08.12.2018
comment
У вас есть для справки образцы предложенного выше решения? Я искал решение той же проблемы и нашел вот это. Вышеупомянутое решение имеет смысл. Что такое класс DataProvider в примере кода? Какая у этого цель? Это кастомный класс? InMemoryDataSource расширит любой из источников данных - PageKeyedDataSource, ItemKeyedDataSource, PositionalDataSource. Это понимание правильное? Было бы здорово, если бы вы могли написать несколько блогов с подробным решением. Я не смог найти в Интернете ничего, кроме этого, что дает некоторую ссылку на решение. - person android.fryo; 25.02.2020
comment
Я экспериментировал с этим подходом, чтобы увидеть, как спроектировать свой уровень данных, возникла пара проблем, например. Наблюдаемость, распространение ошибки, недействительность данных. Я понял, что реализация всего этого самостоятельно ведет к изобретению колеса, поэтому я предпочел бы вместо этого использовать Room - person Mahdi Javaheri; 20.07.2020

Вы можете изменить прямо в своем адаптере, если вы так вызвали currentList

class ItemsAdapter(): PagedListAdapter<Item, ItemsAdapter.ViewHolder(ITEMS_COMPARATOR) {
    ...
    fun changeItem(position: Int,newData:String) {
        currentList?.get(position)?.data = newData
        notifyItemChanged(position)
    }
   
}
person manu    schedule 17.09.2020