DiffUtil ItemCallback areContentsTheSame() always returns true after updating an item on the ListAdapter

前端 未结 4 620
灰色年华
灰色年华 2020-12-18 04:13

While using a ListAdapter I noticed that after updating an item the DiffUtil.ItemCallback areContentsTheSame() method was always returning true. De

4条回答
  •  长情又很酷
    2020-12-18 04:31

    I am also facing this problem recently. For partial ViewHolder changes my approach is like this:

    • when a click is detected in list item of adapter -> delegate it to your view via callback

    • view will delegate this click to ViewModel with click position.

    • ViewModel will update the object property for that position. In my case, I have a List list. Every FeedPost has user information, connect button, like count etc. So if user clicks on Like button, I will increment its value in my ViewModel like list.get(clickedPos).incrementLikeByOne().

    • Now updating the list in ViewModel is also going to reflect the change in your adapter list because the adapter list essentially contains the same object reference. When you are doing add() or addAll() you are not making a deep copy of list items. That's why DiffUtil fails in detecting partial view holder changes.

    • What I did was create a HashMap changeDetailsMap. This map will contain what data has changed in its value and key can be position and use this map inside areContentsTheSame() to return true/false in order to trigger partial ViewHolder changes. The reason I am using an Object so that I can pass anything I want (Integer, String, or my own POJO) but you have to take care of casting it correctly inside DiffUtil. Make sure whatever object you are putting in this HashMap it has an int field (let's say int classType so that you can cast it to correct Class)

    • One thing to note here is you have to use it with SingleLiveEvent like this: SingleLiveEvent> changeDetailsMap_SLE because it's a one-shot operation and not with MutableLiveData as it is sticky in nature. I was using fragment so when fragment gets popped from the backstack then ordinary LiveData again dispatches the recent value because it becomes active when fragment comes out from backstack. You can google SingleLiveEvent if you are not aware of it. You can use it for one-shot operation. Although, one of the googlers in his medium article suggested a better approach, but practically I find this SingleLiveEvent easy to use.

    • push this hashmap to view like: changeDetailsMap_SLE.setValue(changeDetailsMap). You can observe this SingleLiveEvent just like normal LiveData and pass it to your adapter and dispatch it to your DiffUtil. The way I am doing this in my adapter is like this

       public void updateConnectionStatus(Map) {
           List pseudoNewList = new ArrayList<>(adapterList); //adapterList is the list which you already have in your adapter class
           DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new FeedDiffUtil(adapterList, pseudoNewList, updatedConnectionStatusMap));
           adapterList.clear();
           adapterList.addAll(pseudoNewList);
           diffResult.dispatchUpdatesTo(this);
       }
      
    • Also, don't forget to clear this hashmap when work is done in the adapter. You don't want to get the old positions for which you ran DiffUtil earlier when you push again this SingleLiveEvent.

    This approach also works where you are comparing list items (inside areItemsTheSame()) using equals() method and not using object property like oldList.get(oldItemPosition).getPostId() == newList.get(newItemPosition).getPostId(). I have a heterogeneous adapterList in my case so not all list items are guaranteed to have a postID so I can't rely on an object property comparison because it can be null.

提交回复
热议问题