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:
adapter.snapshot()
Returns a list that cannot be modified. We make a copy and modify it to make a new onePagingData
Then submit (submitData
) toadapter
- A cache
PagingData
From 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:
- After we delete a list item, if we refresh the list data, the deleted item should not appear again;
- We can actually delete the target.
The correct deletion method we use depends on two facts:
- 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;
PagingData
Contains all the data for the list (not a single page of data). Only in this way,filter
In order to have the effect of deletion.