I'm using Paging Library to load data from network using ItemKeyedDataSource
. After fetching items user can edit them, this updates are done inside in Memory cache (no database like Room is used).
Now since the PagedList
itself cannot be updated (discussed here) I have to recreate PagedList
and pass it to the PagedListAdapter
.
The update itself is no problem but after updating the recyclerView
with the new PagedList
, the list jumps to the beginning of the list destroying previous scroll position. Is there anyway to update PagedList while keeping scroll position (like how it works with Room)?
DataSource is implemented this way:
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;
}
}
The PagedList created like this:
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();
The PagedListAdapter is created like this:
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);
}
});
, and updated like this:
mAdapter.submitList(pagedList);
P.S: If there is a better way to update list items without using Room please share.
All you need to do is to invalidate current DataSource
each time you update your data.
What I would do:
move networking logic from MentionKeyedDataSource
to new class that extends PagedList.BoundaryCallback
and set it when building your PagedList
.
move all you data to some DataProvider
that holds all your downloaded data and has reference to DataSource
. Each time data is updated in DataProvider
invalidate current DataSource
Something like that maybe
val dataProvider = PagedDataProvider()
val dataSourceFactory = InMemoryDataSourceFactory(dataProvider = dataProvider)
where
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()
}
and
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
}
}
This approach is very similar to what Room
does - every time table row is updated - current DataSource
is invalidated and DataSourceFactory
creates new data source.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With