androidkotlinandroid-recyclerviewandroid-diffutilsandroid-listadapter

DiffUtil in RecyclerView Adapter does not display all items when inserting more than one consecutively


I'm using a RecyclerView along with DiffUtil to manage changes in the data list. The issue I'm facing is that when attempting to insert multiple items consecutively into my adapter using AsyncListDiffer and DiffUtil, not all items are displayed correctly in the RecyclerView. Sometimes only the last inserted item is shown, or only some of the items appear.

Here's how I'm handling item insertion in my adapter:

// No problem showing everything at the beginning
fun addAll(items:List<ItemEntity>){
       asyncListDiffer.submitList(items)
      
    }
 
fun add(item: ItemEntity) {
        val updateList = asyncListDiffer.currentList.toMutableList()
        updateList.add(item)
        asyncListDiffer.submitList(updateList.toList())
     
    }

My implementation of DiffUtil

 private class DiffCallback:DiffUtil.ItemCallback<ItemEntity>(){
        override fun areItemsTheSame(oldItem: ItemEntity, newItem: ItemEntity): Boolean {
            return oldItem.id == newItem.id
        }

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

A part of my viewmodel implementation

@HiltViewModel
class MainViewModel @Inject constructor(private val repository:MainRepository):ScopedViewModel() {

    private var _allItems:MutableLiveData<List<ItemEntity>> = MutableLiveData()
    val allItems:LiveData<List<ItemEntity>> = _allItems

private var _itemInserted:MutableLiveData<ItemEntity> = MutableLiveData()
    val itemInserted:LiveData<ItemEntity> = _itemInserted

 init{
        initScope()
    }
// Called when loading the application
fun fetchAllItems(){
      launch{
          _allItems.value=repository.fetchAllItems()
         }
    }

 fun saveNewItem(itemEntity: ItemEntity){
        launch {
            val idInserted=repository.saveNewItem(itemEntity)
            getItemById(idInserted)
        }
    }
/* Every time I add a new record or multiple records I usually address selected files from mobile storage 
*/
 fun getItemById(itemId:Long){
        launch {
            _itemInserted.value=repository.fetchItemById(itemId)
        }
    }

override fun onCleared() {
        destroyScope()
        super.onCleared()
    }

In my fragment, the first time when loading all the elements are displayed correctly, no problem with that. The observer itemInserted will only be launched if I later add more records to the database, to add them to my adapter.

private fun setUpObservers(){

  mainViewModel.fetchAllSong()

  mainViewModel.allItems.observe(viewLifecycleOwner){
            if (it.isNotEmpty()) {
                adapter.addAll(it)
            }
        }

  mainViewModel.itemInserted.observe(viewLifecycleOwner){item->
            item?.let{
                adapter.add(item)
            }
        }
}

When adding a single record it is normally added to my list the problem comes when I add more than one record consecutively.

As an example, when selecting two or more files and saving their addresses, the itemInserted observer returns each of the records correctly, the problem is that the adapter most of the time only shows the last added element, other times all of them.

private fun mActivityResult(){
launcherOpenMultipleDocs= registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()){uris->
            uris.forEach {uri->
                val realPathFromFile = getRealPathFromURI(uri!!, requireContext())
                mainViewModel.saveNewItem(
                    ItemEntity(
                        pathLocation = realPathFromFile,
                        timestamp = Date().time
                    )
                )
            }
        }
}

I'm not an Android expert so the little I can deduce from this behavior is that by adding several items very quickly to the adapter and since it makes the comparisons of the new elements outside the main thread, there must be some problem with the synchronization, I don't know. I will continue investigating more.

  1. How can I ensure that DiffUtil properly handles multiple consecutive insertions in my RecyclerView?

  2. Are there additional considerations I should be aware of when using AsyncListDiffer and DiffUtil to manage rapid item insertions?

Any help will be welcome thanks.


Solution

  • I finally solved the problem by bringing back all the records every time I add a new one and sending the entire new list to the adapter.

    In MainViewModel class

     fun saveNewItem(itemEntity: ItemEntity){
            launch {
                repository.saveNewItem(itemEntity)
                // I just changed this line
                fetchAllItems()
            }
        }
    
    

    In the fragment the entire list will be added to the adapter again

    mainViewModel.allItems.observe(viewLifecycleOwner){
                if (it.isNotEmpty()) {
                    adapter.addAll(it)
                }
            }
    
    

    In conclusion, although it may initially seem redundant to call the entire list when only one element is added, the efficiency of DiffUtil makes this the best practice to maintain consistency and accuracy in updating your RecyclerView. I must thank @Pawel for the clarification on this matter. If anyone encounters the same issue, I hope this helps.