justanoob
justanoob

Reputation: 1807

PagedList is growing but never shrinking in Android app using Room

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:

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

Answers (1)

justanoob
justanoob

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

Related Questions