Reputation: 1807
In my Android app i have a very basic RecyclerView
implementation that shows entries from a database using Room with the Paging library.
The Dao method that fetches entries from the database:
@Query("SELECT * FROM entries")
abstract fun findAll(): DataSource.Factory<Int, Entry>
And this is how LiveData
is constructed from the returned DataSource.Factory
in my ViewModel
:
private val config = PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(512)
.setPrefetchDistance(256)
.setPageSize(256)
.build()
val entries = LivePagedListBuilder(entryService.findAll(), config).build()
// (entryService here just calls through to the Dao)
I simply observe on this LiveData
and pass the updated PagedList
to my RecyclerView.Adapter
.
The corresponding RecyclerView.Adapter
snippet:
private val differ = AsyncPagedListDiffer<Entry>(this, DIFF_CALLBACK)
fun submitList(list: PagedList<Entry>) {
differ.submitList(list)
}
(i do not use PagedListAdapter
, because i'll need more fine-grained control in my adapter later)
Seemingly everything works fine, but when i started monitor memory usage of the app, i started to suspect that the PagedList
items are not handled properly.
I've added the following Callback
in submitList()
:
list.addWeakCallback(differ.currentList?.snapshot(), object : PagedList.Callback() {
override fun onChanged(position: Int, count: Int) {
Log.d("_tag", "Position: $position | Changed: $count" +
" | Size: ${list.size}")
}
override fun onInserted(position: Int, count: Int) {
Log.d("_tag", "Position: $position | Inserted: $count" +
" | Size: ${list.size}")
}
override fun onRemoved(position: Int, count: Int) {
Log.d("_tag", "Position: $position | Removed: $count" +
" | Size: ${list.size}")
}
})
After this i've inserted 5 000 entries into the database and the corresponding log entry was the following:
D/_tag: Position: 0 | Inserted: 512 | Size: 512
This is fine, this is the expected output.
But as i start scrolling down in the list, the log entries are as follows:
D/_tag: Position: 512 | Inserted: 256 | Size: 768
D/_tag: Position: 768 | Inserted: 256 | Size: 1024
D/_tag: Position: 1024 | Inserted: 256 | Size: 1280
D/_tag: Position: 1280 | Inserted: 256 | Size: 1536
And then the logs stop here, no more log entries, no matter how much i scroll down after this point.
My questions regarding this behavior:
Why onRemoved()
is never called?
Is PagedList
an ever-growing list and the items stay in the memory?
Isn't the point of paging is to load new entries on demand and remove old (not visible) ones as well?
Why do the logs stop after the 1536th entry, even if i scroll way past that entry?
The strange thing is, the entries are displayed properly, even if a scroll all the way to the bottom of the list.
I'd really appreciate if someone could explain the behavior to me.
(Java answers are welcome as well)
EDIT:
The code of the whole adapter:
class EntryListAdapter
: RecyclerView.Adapter<EntryListAdapter.EntryViewHolder>() {
private val differ = AsyncPagedListDiffer<Entry>(this, DIFF_CALLBACK)
fun submitList(list: PagedList<Entry>) {
differ.submitList(list)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
EntryViewHolder {
val inflater = LayoutInflater.from(parent.context)
val entryItemView = inflater.inflate(R.layout.item_entry, parent, false)
return EntryViewHolder(entryItemView)
}
override fun onBindViewHolder(holder: EntryViewHolder, position: Int) {
differ.getItem(position)?.let { holder.bindTo(it) }
}
override fun getItemId(position: Int): Long {
return differ.getItem(position)?.id ?: RecyclerView.NO_ID
}
override fun getItemCount(): Int {
return differ.currentList?.size ?: 0
}
inner class EntryViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
fun bindTo(entry: Entry) {
itemView.textEntryTitle.text = entry.title
}
}
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Entry>() {
override fun areItemsTheSame(entry1: Entry, entry2: Entry): Boolean {
return entry1.id == entry2.id
}
override fun areContentsTheSame(entry1: Entry, entry2: Entry): Boolean
{
return entry1 == entry2
}
}
}
}
EDIT2:
The layout of the Fragment
that hosts the RecyclerView
:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Upvotes: 1
Views: 501
Reputation: 1807
Chris Craik of the Android Architecture Components teams has just confirmed on Reddit that as of yet the Paging library do not drop paged data (but it's coming):
It is merged (1 and 2), but we haven't released it yet due to being extra-careful during the AndroidX switchover. Arch had to be careful to avoid causing regressions during an AndroidX migration (since those migrations are complex enough).
Now that AndroidX is stable, we can do the release, so we'll see about having it out soon.
Upvotes: 1