问题
I'm currently looking into incorporating the Paging Architecture library (version 2.1.0-beta01
at the time of writing) into my app. One components is a list which allows the user to delete individual items from it. This list is network-only and caching localy with Room does not make sense.
PagedList
is immutable and does not support modification. I have read that having a copy of the list which is than modified and returned as the new one is the way to go. The documentation states the same:
If you have more granular update signals, such as a network API signaling an update to a single item in the list, it's recommended to load data from the network into memory. Then present that data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the snapshot can be created.
I currently have the basic recommended implementation to show a simple list. My DataSource
looks like this:
class MyDataSource<SomeItem> : PageKeyedDataSource<Int, SomeItem>() {
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, SomeItem>) {
// Simple load from API and notification of `callback`.
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, SomeItem>) {
// Simple load from API and notification of `callback`.
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, SomeItem>) {
// Simple load from API and notification of `callback`.
}
}
How would a concrete implementation of an in-memory cache (without Room and without invalidating the entire dataset) as referenced in the documentation look like?
回答1:
You are correct in that a DataSource
is meant to hold immutable data.
I believe this is because Room and Paging Library is trying to have more opinionated design decisions and advocate for immutable data.
This is why in the official docs, they have a section for updating or mutating your dataset should invalidate the datasource when such a change occurs.
Updating Paged Data: If you have more granular update signals, such as a network API signaling an update to a single item in the list, it's recommended to load data from network into memory. Then present that data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the snapshot can be created.
Source: https://developer.android.com/reference/android/arch/paging/DataSource
With that in mind, I believe it's possible to solve the problem you described using a couple of steps.
This may not be the cleanest way, as it involves 2 steps.
You can get a reference the the snapshot that the PagedList is holding, which is a type MutableList
. Then, you can just remove or update the item inside that snapshot, without invalidating the data source.
Then step two would be to calling something like notifyItemRemoved(index)
or notifyItemChanged(index)
.
Since you can't force the DataSource to notify the observers of the change, you'll have to do that manually.
pagedList.snapshot().remove(index) // Removes item from the pagedList
adapter.notifyItemRemoved(index) // Triggers recyclerview to redraw/rebind to account for the deleted item.
There maybe a better solution found in your DataSource.Factory
.
According to the official docs, your DataSource.Factory
should be the one to emit a new PagedList
once the data is updated.
Updating Paged Data: To page in data from a source that does provide updates, you can create a DataSource.Factory, where each DataSource created is invalidated when an update to the data set occurs that makes the current snapshot invalid. For example, when paging a query from the Database, and the table being queried inserts or removes items. You can also use a DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data, you can connect an explicit refresh signal to call invalidate() on the current DataSource.
Source: https://developer.android.com/reference/android/arch/paging/DataSource
I haven't found a good solution for this second approach however.
回答2:
If you want to modify your list without going all the way down to the data layer, you will need to override submitList
in your adapter, and then set a callback on your PagedList
object. Whenever the PagedList
changes, you can then copy those changes to your local dataset. This is not recommended but it's a pretty minimal hack to get working.
Here's an example:
class MyListAdapter : PagedListAdapter<MyDataItem, MyViewHolder>(MyDiffCallback()) {
/**
* This data set is a bit of a hack -- we are copying everything the PagedList loads into our
* own list. That way we can modify it. The docs say you should go all the way down to the
* data source, modify it there, and then bubble back up, but I don't think that will actually
* work for us when the changes are coming from the UI itself.
*/
private val dataSet = arrayListOf<MyDataItem>()
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
//Forces the next page to load when we reach the bottom of the list
getItem(position)
dataSet.getOrNull(position)?.let {
holder.populateFrom(it)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = parent.inflate(R.layout.my_view_holder)
return MyViewHolder(view)
}
class MyDiffCallback : DiffUtil.ItemCallback<MyDataItem>() {
override fun areItemsTheSame(oldItem: MyDataItem, newItem: MyDataItem) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: MyDataItem, newItem: MyDataItem) =
oldItem == newItem
}
override fun submitList(pagedList: PagedList<MyDataItem>?) {
pagedList?.addWeakCallback(listOf(), object : PagedList.Callback() {
override fun onChanged(position: Int, count: Int) {
dataSet.clear()
dataSet.addAll(pagedList)
}
override fun onInserted(position: Int, count: Int) {
dataSet.clear()
dataSet.addAll(pagedList)
}
override fun onRemoved(position: Int, count: Int) {
dataSet.clear()
dataSet.addAll(pagedList)
}
})
super.submitList(pagedList)
}
}
来源:https://stackoverflow.com/questions/53243704/modifying-pagedlist-in-android-paging-architecture-library