Paging3 is Google’s library for users to load paging data. But here’s the surprise — he doesn’t provide an interface to delete it. In some cases, we simply need to delete data. What can we do?

This article mainly provides what I think is a reasonable and efficient way to remove items from the Paging3 list, without touching on other uses of Paging3.

Build a simple test APP

PagingSource

data class User(val uid: Long.val name: String)

class NamePagingSource(
    private val max: Long
) : PagingSource<Long, User>() {

    override fun getRefreshKey(state: PagingState<Long, User>): Long {
        return 0
    }

    override suspend fun load(params: LoadParams<Long>): LoadResult<Long, User> {
        valbegin = params.key ? :0
        val end = (begin + params.loadSize).coerceAtMost(max)
        val data = (begin until end).map {
            User(it, "Name@$it")}val prevCursor = if (begin == 0L) null else begin
        val nextCursor = if (end < max) end else null
        return LoadResult.Page(data, prevCursor, nextCursor)
    }
}
Copy the code

ViewModel

class UserViewModel : ViewModel() {
    companion object {
        private const val LIMIT = 100L
        private const val PAGE_SIZE = 10
    }

    private var dataFlow: Flow<PagingData<User>>? = null

    fun getDataFlow(a): Flow<PagingData<User>> {
        returndataFlow ? : Pager( config = PagingConfig(PAGE_SIZE), initialKey =0,
            pagingSourceFactory = {
                NamePagingSource(LIMIT)
            }
        ).flow.cachedIn(viewModelScope).also {
            dataFlow = it
        }
    }
}
Copy the code

Adapter

class UserListAdapter(
    private val deletionCallback: (Int, User) -> Unit
) : PagingDataAdapter<User, UserListAdapter.NameHolder>(COMPARATOR) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NameHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = UserListItemBinding.inflate(inflater, parent, false)
        return NameHolder(binding)
    }

    override fun onBindViewHolder(holder: NameHolder, position: Int) {
        holder.bind(position, getItem(position)!!)
    }

    inner class NameHolder(
        private val binding: UserListItemBinding
    ) : RecyclerView.ViewHolder(binding.root), View.OnLongClickListener {

        private var index = -1
        private var user: User? = null

        init {
            binding.root.setOnLongClickListener(this)}fun bind(index: Int, user: User) {
            this.index = index
            this.user = user
            binding.name.text = user.name
        }

        override fun onLongClick(v: View): Boolean {
            valuser = user ? :return false
            deletionCallback(index, user)
            return true}}companion object {
        private val COMPARATOR = object : DiffUtil.ItemCallback<User>() {
            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem.uid == newItem.uid
            }

            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem == newItem
            }
        }
    }
}
Copy the code

Layout is a simple TextView:


      
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/name"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textColor="# 222222"
    android:textSize="24sp"
    android:paddingTop="10dp"
    android:paddingBottom="10dp"
    android:paddingStart="16dp"
    android:paddingEnd="16dp"
    tools:text="ffff"
    />
Copy the code

RecyclerView initialization

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var viewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        viewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        initUserList()
    }

    private fun initUserList(a) {
        binding.userList.layoutManager = LinearLayoutManager(this)
        val adapter = UserListAdapter { position, user ->
            // TODO delete it
        }
        binding.userList.adapter = adapter
        lifecycleScope.launch {
            viewModel.getDataFlow().collectLatest {
                adapter.submitData(it)
            }
        }
    }
}
Copy the code

Thus, a simple application based on Paging3 is built:

How do I delete a list item?

To delete a list item, we first need to get the data for the current list. There are two ways to do this:

  1. adapter.snapshot()Returns a list that cannot be modified. We make a copy and modify it to make a new onePagingDataThen submit (submitData) toadapter
  2. A cachePagingDataFrom which to get deleted list data

I chose method two here, so we don’t have to worry too much about the implementation details of PagingData. You can delete a list item as follows:

class MainActivity : AppCompatActivity() {

    private lateinit var adapter: UserListAdapter
    private lateinit var pagingData: PagingData<User>

    // ...

    private fun initUserList(a) {
        binding.userList.layoutManager = LinearLayoutManager(this)
        binding.userList.adapter = UserListAdapter(::deleteItem).also {
            adapter = it
        }
        lifecycleScope.launch {
            viewModel.getDataFlow().collectLatest {
                pagingData = it
                adapter.submitData(it)
            }
        }
    }

    private fun deleteItem(position: Int, user: User){ lifecycleScope.launch { pagingData = pagingData.filter { it ! == user } adapter.submitData(pagingData) } } }Copy the code

The effect is as follows:

What about performance?

Some of you might think that direct filter is inefficient but it’s not. The time required for filter is θ (n), and the time required for deleting ArrayList is two o (n). At worst, they’re the same; On average, there are twice as many filters, but the order of magnitude is the same; In addition, the number of items in the list data is usually not very large, so a filter is acceptable.

How correct?

Correctness includes the following two points:

  1. After we delete a list item, if we refresh the list data, the deleted item should not appear again;
  2. We can actually delete the target.

The correct deletion method we use depends on two facts:

  1. The list data presented by the client is actually a copy of the backend data. In the case that the local copy needs to be deleted, the user must have done something that made the list item invalid. Pull data from the back end again, and the list item will no longer appear;
  2. PagingDataContains all the data for the list (not a single page of data). Only in this way,filterIn order to have the effect of deletion.