Modifying PagedList in Android Paging Architecture library

rubengees picture rubengees · Nov 10, 2018 · Viewed 8.7k times · Source

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?

Answer

PhillyTheThrilly picture PhillyTheThrilly · Jul 22, 2019

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