PagedListAdapter does not update list if just the

2019-07-24 05:23发布

问题:

I'm using the Room and Paging libraries to display categories.

My Entity:

@Entity(tableName = Database.Table.CATEGORIES)
data class Category(
    @PrimaryKey(autoGenerate = true) @ColumnInfo(name = ID) var id: Long = 0,
    @ColumnInfo(name = NAME) var name: String = "",
    @ColumnInfo(name = ICON_ID) var iconId: Int = 0,
    @ColumnInfo(name = COLOR) @ColorInt var color: Int = DEFAULT_COLOR
)

My DAO:

@Query("SELECT * FROM $CATEGORIES")
fun getPagedCategories(): DataSource.Factory<Int, Category>

@Update
fun update(category: Category)

My Repo:

val pagedCategoriesList: LiveData<PagedList<Category>> = categoryDao.getPagedCategories().toLiveData(Config(CATEGORIES_LIST_PAGE_SIZE))

My ViewModel:

val pagedCategoriesList: LiveData<PagedList<Category>>
    get() = repository.pagedCategoriesList

My Adapter:

class CategoriesAdapter(val context: Context) : PagedListAdapter<Category, CategoriesAdapter.CategoryViewHolder>(CategoriesDiffCallback()) {

    //region Adapter

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
        return CategoryViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_category, parent, false))
    }

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

    //endregion

    //region Methods

    fun getItemAt(position: Int): Category = getItem(position)!!

    //endregion

    inner class CategoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        private val iconHelper = IconHelper.getInstance(context)

        fun bind(category: Category) {
            with(itemView) {
                txvCategoryItemText.text = category.name
                imvCategoryItemIcon.setBackgroundColor(category.color)
                iconHelper.addLoadCallback {
                    imvCategoryItemIcon.setImageDrawable(iconHelper.getIcon(category.iconId).getDrawable(context))
                }
            }
        }
    }

    class CategoriesDiffCallback : DiffUtil.ItemCallback<Category>() {

        override fun areItemsTheSame(oldItem: Category, newItem: Category): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Category, newItem: Category): Boolean {
            return oldItem == newItem
        }
    }
}

And my Fragment:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    categoryViewModel = ViewModelProviders.of(this).get(CategoryViewModel::class.java)

    adapter = CategoriesAdapter(requireContext())
    categoryViewModel.pagedCategoriesList.observe(this, Observer(adapter::submitList))
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    ViewCompat.setTooltipText(fabNewCategory, getString(R.string.NewCategory))

    with(mRecyclerView) {
        layoutManager = GridLayoutManager(requireContext(), 4)
        itemAnimator = DefaultItemAnimator()
        addItemDecoration(SpacesItemDecoration(resources.getDimensionPixelSize(R.dimen.card_default_spacing)))

        addOnItemTouchListener(OnItemTouchListener(requireContext(), this, this@CategoriesFragment))
    }

    mRecyclerView.adapter = adapter

    fabNewCategory.setOnClickListener(this)
}

Everything works when inserting, deleting or just loading categories. But when I'm updating a single entity's color or text, the list is not updated, though submit list is called correctly.

I debugged the whole process and found the problem: After submitting the list, AsyncPagedListDiffer#submitList is called. I compared the previous list (mPagedList in AsyncPagedListDiffer) and the new list (pagedListin AsyncPagedListDiffer#submitList). The items I edited there are equal and do already hold the new data. So DiffUtil compares everything and the items are already equal though the displayed list is not updated.

If the list is a reference, it would explain why the data is already refreshed in the adapters list, but how do I solve the issue then?

回答1:

I think the problem is not the way you are loading the new data, but updating the data. Although you haven't show us the part where you triggers item update or how the actual update happens, I am guessing, sorry if I was wrong, you might be directly editting the list element like this:

category = adapter.getItemAt(/*item position*/)
category.name = "a new name"
category.color = 5
categoryViewModel.update(category)


Instead, you should create a new Category object instead of modifying existing ones, like this:

prevCategory = adapter.getItemAt(/*put position*/) // Do not edit prevCategory!
newCategory = Category(id=prevCategory.id, name="a new name", color=5, iconId=0)
categoryViewModel.update(newCategory)


The idea of creating a whole new fresh object every time you want to make even the smallest change is something might not be so obvious at first, but immutability is a key part of Functional Reactive Programming, and a lot of reactive components rely on to immutability of the data being processed.

What I like to do to avoid this kind of mistake, I always make every field in my data class final.

@Entity(tableName = Database.Table.CATEGORIES)
data class Category(
    @PrimaryKey(autoGenerate = true) @ColumnInfo(name = ID) val id: Long = 0,
    @ColumnInfo(name = NAME) val name: String = "",
    @ColumnInfo(name = ICON_ID) val iconId: Int = 0,
    @ColumnInfo(name = COLOR) @ColorInt val color: Int = DEFAULT_COLOR
)


回答2:

Nobody is able to answer your question unless you show your Dao and pagedlistadapter class which contains DiffUtill.itemcallaback. I show you some code might that help.

  1. you have to implement update in your DAO interface like this:

    @Update fun updateUsers(data: MyData)

if you have this method after that you check your diffcall back like below:

companion object {
    val videosDiffCallback = object : DiffUtil.ItemCallback<Item>(){
        override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
            return oldItem.id == newItem.id //Called to decide whether two objects(new and old items) represent the same item.
        }

        override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
            return oldItem == newItem //Called to decide whether two items have the same data.
        }
    }
}
//oldItem   Value: The item in the old list.
//newItem   Value: The item in the new list.


回答3:

I have made a RnD on PagedListAdapter and custom paging with Room database. Click here and you will found my implementation. Hope this will help you.

Thanks.