sockeqwe / AdapterDelegates

"Favor composition over inheritance" for RecyclerView Adapters
http://hannesdorfmann.com/android/adapter-delegates
Apache License 2.0
2.93k stars 313 forks source link

Adapter not showing items when using DiffUtil #43

Closed curiousily closed 5 years ago

curiousily commented 7 years ago

Hi,

I've been playing around with DiffUtil and AdapterDelegates but can't seem to show the items using those two. However, running DIffUtil with "raw" RecyclerView.Adapter works fine. Here is a snippet of the code I am using:

Adapter

class RewardListAdapter(manager: AdapterDelegatesManager<List<RewardViewModel>>) : 
            ListDelegationAdapter<List<RewardViewModel>>(manager)

Delegate

    class RewardAdapterDelegate(private val inflater: LayoutInflater,
                                private val removeSubject: PublishSubject<RemoveRewardFromListUseCase.Parameters>) : AdapterDelegate<List<RewardViewModel>>() {

        override fun onBindViewHolder(items: List<RewardViewModel>, position: Int, holder: RecyclerView.ViewHolder, payloads: MutableList<Any>) {
            val vh = holder as RewardViewHolder
            val reward = items[position]
            vh.bindReward(reward, items)
        }

        override fun isForViewType(items: List<RewardViewModel>, position: Int): Boolean = true

        override fun onCreateViewHolder(parent: ViewGroup?): RecyclerView.ViewHolder =
            RewardViewHolder(inflater.inflate(R.layout.item_reward, parent, false))

        inner class RewardViewHolder(view: View) : RecyclerView.ViewHolder(view) {

            fun bindReward(rewardView: RewardViewModel, items: List<RewardViewModel>) {
                with(rewardView) {
                    RxView.clicks(itemView.delete)
                        .map { RemoveRewardFromListUseCase.Parameters(items, rewardView) }
                        .subscribe(removeSubject)
                    itemView.name.text = name
                    itemView.description.text = description
                }
            }
        }

    }

Setting up

val delegatesManager = AdapterDelegatesManager<List<RewardViewModel>>()
            .addDelegate(RewardAdapterDelegate(LayoutInflater.from(activity), removeRewardSubject))

adapter = RewardListAdapter(delegatesManager)
rewardList.adapter = adapter

Updating with new data

val oldList = adapter.items?.toMutableList() ?: mutableListOf()
val newList = state.rewards.toMutableList()

val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {

            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
                        oldList[oldItemPosition].id == newList[newItemPosition].id

            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
                        oldList[oldItemPosition] == newList[newItemPosition]

            override fun getOldListSize() = oldList.size

            override fun getNewListSize() = newList.size
})
diff.dispatchUpdatesTo(adapter)

After the call to dispatchUpdatesTo nothing happens. The list is blank.

For the "raw" adapter I use the approach as defined here:

https://github.com/antoniolg/diffutil-recyclerview-kotlin/blob/master/app/src/main/java/com/antonioleiva/diffutilkotlin/AutoUpdatableAdapter.kt

and my adapter looks like this:

class RewardListAdapter(val deleteSubject: PublishSubject<RemoveRewardFromListUseCase.Parameters>) : 
RecyclerView.Adapter<RewardListAdapter.RewardViewHolder>(), AutoUpdatableAdapter {

        var rewardList: MutableList<RewardViewModel> by Delegates.observable(mutableListOf()) { _, old, new ->
            autoNotify(old, new) { o, n -> o.id == n.id }
        }

        override fun getItemCount(): Int = rewardList.size

        override fun onBindViewHolder(holder: RewardViewHolder, position: Int) {
            val post = rewardList[position]
            holder.bind(post)
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RewardViewHolder =
            RewardViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_reward, parent, false))

        inner class RewardViewHolder(view: View) : RecyclerView.ViewHolder(view) {

            fun bind(rewardView: RewardViewModel) {
                with(rewardView) {
                    RxView.clicks(itemView.delete)
                        .map { RemoveRewardFromListUseCase.Parameters(rewardList, rewardView) }
                        .subscribe(deleteSubject)
                    itemView.name.text = name
                    itemView.description.text = description
                }
            }
        }
    }

Running somewhat similar code using a "raw" adapter works just fine. Any thoughts? Am I doing something wrong?

Thanks for the awesome lib!

curiousily commented 7 years ago

UPDATE

If I do this:

adapter.items = newList
diff.dispatchUpdatesTo(adapter)

I do get the list to show. However, after trying to delete the second item - the list is restarted to its initial state. Again, that behaviour is not observed using "raw" adapter - everything there works just fine. Maybe I am doing the update incorrectly?

sockeqwe commented 7 years ago

Hm, that sounds strange. AdapterDelegates is not doing something special here.

Is your app open source so that I can debug / reproduce it more easily? which version of AdaterDelegate / RecyclerView are you using?

Again, that behaviour is not observed using "raw" adapter - everything there works just fine.

With the same DiffUtil.Callback implementation?

curiousily commented 7 years ago

Hi @sockeqwe,

Yes. It is open source. I've setted it up as nicely as I could for you to replicate it:

Clone this branch:

https://github.com/iPoli/iPoli-android/tree/kotlin

(I am using Android Studio Beta 3)

Adapter delegates version: com.hannesdorfmann:adapterdelegates3:3.0.1 RecyclerView version: com.android.support:recyclerview-v7:26.0.1

The Callback implementation looks the same to me.

Steps to reproduce:

  1. Run the app
  2. Continue as Guest
  3. Press the bin icon on any 2 cards, without waiting for the snackbar to disappear - should observe normal behavior
  4. Remove the app
  5. Uncomment all the AdapterDelegates staff and comment the "raw" adapter onCreateView and render.
  6. Repeat 1., 2. and 3. - should observe some strange updates

Btw, any feedback on the MVI implementation would be greatly appreciated!

Thank you for looking into the issue!

Regards

sockeqwe commented 7 years ago

Thanks, i will take a look tomorrow.

sockeqwe commented 7 years ago

Sorry for the long silence. Unfortunately, I haven't had time yet to checkout your source code (but the UI looks gorgeous! well done!).

However, I hacked together a little demo that shows that the DiffUtils and animations are working as intended. See here (part of this repository, you can launch the sample app):

https://github.com/sockeqwe/AdapterDelegates/blob/master/app/src/main/java/com/hannesdorfmann/adapterdelegates3/sample/animations/AnimationDiffUtilsActivity.java

So AdapterDelegates is not really doing anything special here when it comes to applying DiffUtil.Results. it just forwards the adapter calls to the corresponding AdapterDelegate. Therefore it sounds pretty strange that you face an issue with DiffUtils with AdapterDelegates, but not by using "raw" adapter.

Just a guess into the blue. Is your data immutable (incl. the list itself) when calculating the diff result?

sockeqwe commented 6 years ago

Can this be closed?