Thelgon
Thelgon

Reputation: 15

DiffUtil (dispatchUpdatesTo) not updating Recycler View

I am implementing a recycler view with diff util, but the first time I send the list to the adapter, the recycler view is not notified about the new list. Only the second time I update the list does the recycler receive the notification. I tried several solutions I found in similar questions but none worked. If anyone can help, thanks in advance.

Here is my code:

Adapter:

class UserListAdapter : Adapter<UserListItemViewHolder>() {
        
    private var mUsers = mutableListOf<User>()
        
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListItemViewHolder =
        UserListItemViewHolder.create(parent)
        
    override fun onBindViewHolder(holder: UserListItemViewHolder, position: Int) {
        holder.bind(mUsers[position])
    }
        
    override fun getItemCount(): Int = mUsers.size
        
    fun updateUsersList(users: List<User>) {
        val diffCallback = UserListDiffCallback(this.mUsers, users)
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        
        this.mUsers.clear()
        this.mUsers.addAll(users)
        diffResult.dispatchUpdatesTo(this)
    }
}

UsersListDiffCallback:

class UserListDiffCallback(
    private val oldList: List<User>,
    private val newList: List<User>
) : DiffUtil.Callback() {
    
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldList[oldItemPosition].username == newList[newItemPosition].username
    }
    
    override fun getOldListSize(): Int {
        return oldList.size
    }
    
    override fun getNewListSize(): Int {
        return newList.size
    }
    
    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return true
    }
}

ViewModel:

@HiltViewModel
class MainViewModel @Inject constructor(
    private val getUsersUseCase: GetUsersUseCase
) : ViewModel() {

    private val _usersResultStatus = MutableLiveData<ResultStatus>(NotLoading)
    val usersResultStatus: LiveData<ResultStatus>
        get() = _usersResultStatus

    private val _users = MutableLiveData<MutableList<User>?>()
    val users: LiveData<MutableList<User>?>
        get() = _users

    fun getUsers() {
        viewModelScope.launch {
            try {
                _usersResultStatus.value = Loading
                _users.value = getUsersUseCase()
            } catch (error: Throwable) {
                _usersResultStatus.value = Error(error)
            } finally {
                _usersResultStatus.value = NotLoading
            }
        }
    }
}

And where I update the list in the activity:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    private var _binding: ActivityMainBinding? = null
    private val binding: ActivityMainBinding get() = _binding!!
    
    private val viewModel: MainViewModel by viewModels()
    private val usersAdapter = UserListAdapter()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    
        initUsersAdapter()
        observeStates()
        viewModel.getUsers()
    }
    
    private fun initUsersAdapter() {
        with(binding.recyclerView) {
            setHasFixedSize(true)
            adapter = usersAdapter
        }
    }
    
    private fun observeStates() {
        viewModel.usersResultStatus.observe(this) { resultStatus ->
            setProgressbarVisibility(resultStatus is Loading)
    
            if (resultStatus is Error) {
                Toast.makeText(
                    this@MainActivity,
                    getString(R.string.error),
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    
        viewModel.users.observe(this) { users ->
            users?.let {
                usersAdapter.updateUsersList(users.toMutableList())
            }
        }
    }
    
    private fun setProgressbarVisibility(show: Boolean) {
        binding.progressBar.visibility = if (show) View.VISIBLE else View.GONE
    }
}

Upvotes: 1

Views: 521

Answers (1)

Trying to add item range changed to your code works like this for me

fun updateUsersList(users: List<User>) {
        val diffCallback = UserListDiffCallback(this.mUsers, users)
        val diffResult = DiffUtil.calculateDiff(diffCallback)
        
        this.mUsers.clear()
        this.mUsers.addAll(users)
        diffResult.dispatchUpdatesTo(this)
        notifyItemRangeChanged(0, users.size)
 
    }

Upvotes: 0

Related Questions