androidandroid-recyclerviewandroid-diffutils

How to use DiffUtils In RecyclerViewAdapter on Android


In my application I want use RecyclerView adapter and for set data I used DiffUtils.
I Want search data from server and then show it into RecyclerView!
I write below codes, but after search data show items overlay!
I want first clear previous data, then add new items!

Fragment codes :

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    //InitViews
    binding.apply {
        searchEdt.addTextChangedListener {
            val search = it.toString()
            if (search.isNotEmpty()) {
                viewModel.searchList.observe(viewLifecycleOwner) { response ->
                    lastMoviesAdapter.differ.submitList(response.data)
                    searchMoviesRecycler.initRecycler(LinearLayoutManager(requireContext()), lastMoviesAdapter)
                }
                viewModel.loadSearchMovies(search)
            } else {
                Snackbar.make(view, getString(R.string.fillAllFields), Snackbar.LENGTH_SHORT).show()
            }
        }

Adapter codes:

    class LastMoviesAdapter @Inject constructor() : RecyclerView.Adapter<LastMoviesAdapter.ViewHolder>() {

    private lateinit var binding: ItemHomeMoviesLastBinding

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        binding = ItemHomeMoviesLastBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder()
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(differ.currentList[position])
        holder.setIsRecyclable(false)
    }

    override fun getItemCount() = differ.currentList.size

    inner class ViewHolder : RecyclerView.ViewHolder(binding.root) {

        @SuppressLint("SetTextI18n")
        fun bind(item: Data) {
            binding.apply {
                moviePosterImg.load(item.poster) {
                    crossfade(true)
                    crossfade(1000)
                }
                movieNameTxt.text = item.title
                movieRateTxt.text = item.imdbRating
                movieCountryTxt.text = item.country
                movieYearTxt.text = item.year
            }
        }
    }

    private val differCallback = object : DiffUtil.ItemCallback<Data>() {
        override fun areItemsTheSame(oldItem: Data, newItem: Data): Boolean {
            return oldItem.id == newItem.id
        }

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

    }

    val differ = AsyncListDiffer(this, differCallback)
}

How can I fix it?


Solution

  • create a diff utils like this (can do in adapter class)

     class DiffUtilCallback(private val oldList: List<Any>, private val newList: List<Any>) :
            DiffUtil.Callback() {
    
            // old size
            override fun getOldListSize(): Int = oldList.size
    
            // new list size
            override fun getNewListSize(): Int = newList.size
    
            // if items are same
            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                val oldItem = oldList[oldItemPosition]
                val newItem = newList[newItemPosition]
                return oldItem.javaClass == newItem.javaClass
            }
    
            // check if contents are same
            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
                val oldItem = oldList[oldItemPosition]
                val newItem = newList[newItemPosition]
    
                return oldItem.hashCode() == newItem.hashCode()
            }
        }
    

    then create two methods in your adapter

    // set data
        fun setData(data: List<Any>) {
            this.data = data.toMutableList()
        }
    
        // add new data
        fun setNewData(newData: List<Any>) {
            val diffCallback = DiffUtilCallback(data, newData)
            val diffResult = DiffUtil.calculateDiff(diffCallback)
            data.clear()
            data.addAll(newData)
            diffResult.dispatchUpdatesTo(this)
        }
    

    in fragment/activity do this

    adapter = AdapterDual(recycler, lifecycleScope) // init adapter
    adapter.setData(list) // set data
    recycler.layoutManager = LinearLayoutManager(activity)
    recycler.adapter = adapter // set adapter on recycler
    
    // and when you load new data or replace it
    adapter.setNewData(newList)
    

    remember that if you are loading more items then newList will contain both the previous items as well as loaded items