AndroidDev123
AndroidDev123

Reputation: 370

DiffUtil is changing my position when list is sortedByDescending

I am using diff util to improve performance in my recyclerview as opposed to calling notifyDataSetChanged(). The recyclerview has a header with some chips which can reorder the list by aplhabetical order, highester score etc

When I click the chip in the header and the list reorders its effecting my position. For example if I have the ordered by hightest score and score 100% is position 1 (after the header) and I click the chip to reorder in reverse. Score 100% will now be at the bottom of the list and 0% will be at the top. But now I have to scroll all the way back to the top to see the header chips again. I want the list to reorder but I dont want my on screen position to change.

enter image description here

Here is my adapter code:

class DigitalTestsResultsAdapter(
private val interaction: Interaction? = null,
private val dateUtil: DateUtil,
private val theme: ThemeModel?,
private val username: String?
) : ListAdapter<ResultResponseModel, RecyclerView.ViewHolder>(ResultsDiffCallBack()) {

private val itemViewTypeHeader: Int = 0
private val itemViewTypeItem: Int = 1
private var filteredList = emptyList<ResultResponseModel>()
private val adapterScope = CoroutineScope(Dispatchers.Default)

class ResultsDiffCallBack : DiffUtil.ItemCallback<ResultResponseModel>() {
    override fun areItemsTheSame(
        oldItem: ResultResponseModel,
        newItem: ResultResponseModel
    ): Boolean {
        return oldItem.certificateUrl == newItem.certificateUrl
    }

    @SuppressLint("DiffUtilEquals")
    override fun areContentsTheSame(
        oldItem: ResultResponseModel,
        newItem: ResultResponseModel
    ): Boolean {
        return oldItem == newItem
    }
}

fun filterList(list: List<ResultResponseModel>, type: String) {
    adapterScope.launch {
        when (type) {

            "courseName" -> {
                filteredList = list.sortedBy { it.courseName }
            }

            "isCpd" -> {
                filteredList = list.sortedBy { it.courseName }.sortedByDescending { it.isCPD }
            }

            "organisationName" -> {
                filteredList = list.sortedBy { it.organisationName }
            }

            "roleName" -> {
                filteredList = list.sortedBy { it.roleName }
            }

            "score" -> {
                filteredList = list.sortedByDescending { it.score }
            }

            "submitTime" -> {
                filteredList = list.sortedByDescending { it.submitTime }
            }
        }
        withContext(Dispatchers.Main) {
            submitList(filteredList)
        }
    }
}


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    return when (viewType) {

        itemViewTypeHeader -> {
            DigitalTestsResultsHeaderViewHolder(
                RvDigitalTestResultsHeaderBinding.inflate(
                    LayoutInflater.from(parent.context),
                    parent,
                    false
                )
            )
        }

        itemViewTypeItem -> {
            DigitalTestsResultsViewHolder(
                RvDigitalTestsResultsBinding.inflate(
                    LayoutInflater.from(parent.context),
                    parent,
                    false
                ),
                interaction = interaction
            )
        }

        else -> throw ClassCastException("Unknown viewType $viewType")

    }
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    when (holder) {

        is DigitalTestsResultsHeaderViewHolder -> {
            holder.bind()
        }

        is DigitalTestsResultsViewHolder -> {
            holder.bind(currentList[position])
        }

    }
}

override fun getItemViewType(position: Int): Int {
    return if (position == 0) {
        itemViewTypeHeader
    } else {
        itemViewTypeItem
    }
}

override fun getItemCount(): Int {
    return if (!currentList.isNullOrEmpty()) {
        currentList.size
    } else 0
}

inner class DigitalTestsResultsHeaderViewHolder
constructor(
    private val binding: RvDigitalTestResultsHeaderBinding
) : RecyclerView.ViewHolder(binding.root) {

    fun bind() {
        with(binding) {
            with(theme) {

                userName.text = itemView.context.getString(R.string.hi_username, username)
                userName.setTextColourHex(this?.textModel?.primaryColor)
                chipCv.setCardBackgroundColourHex(this?.interfaceModel?.secondaryColor)

                testsChipGroup.setOnCheckedChangeListener { _, checkedId ->
                    when (checkedId) {

                        R.id.chipCertified -> {
                            chipCertified.isChecked = true
                            filterList(currentList, "isCpd")
                        }

                        R.id.chipCourse -> {
                            chipCourse.isChecked = true
                            filterList(currentList, "courseName")
                        }

                        R.id.chipHighestScore -> {
                            chipHighestScore.isChecked = true
                            filterList(currentList, "score")
                        }

                        R.id.chipRecent -> {
                            chipRecent.isChecked = true
                            filterList(currentList, "submitTime")
                        }

                        R.id.chipRole -> {
                            chipRole.isChecked = true
                            filterList(currentList, "roleName")
                        }

                        R.id.chipSchoolName -> {
                            chipSchoolName.isChecked = true
                            filterList(currentList, "organisationName")
                        }
                        else -> {

                        }
                    }
                }
            }
        }
    }
}

inner class DigitalTestsResultsViewHolder
constructor(
    private val binding: RvDigitalTestsResultsBinding,
    private val interaction: Interaction?
) : RecyclerView.ViewHolder(binding.root) {

    @SuppressLint("SetTextI18n")
    fun bind(item: ResultResponseModel?) {
        with(binding) {
            with(theme) {

                viewCertificateBtn.setOnClickListener {
                    interaction?.onItemSelected("certificateBtn", absoluteAdapterPosition, item)
                }

                retakeTestBtn.setOnClickListener {
                    interaction?.onItemSelected("retakeTestBtn", absoluteAdapterPosition, item)
                }

                resultsProgressBar.progress = item?.score?.toFloat() ?: 0f

                if (isValidHex(item?.roleColour)) {
                    resultsProgressBar.circleProgressColor = Color.parseColor(item?.roleColour)
                    resultsProgressBar.pointerColor = Color.parseColor(item?.roleColour)
                }

                score.text = item?.score.toString() + "%"
                title.text = item?.courseName
                date.text = dateUtil.formatStringDateToDDMMYYYY(item?.submitTime)
                role.text = item?.roleName
                schoolName.text = item?.organisationName

                title.setTextColourHex(this?.textModel?.primaryColor)
                retakeTestBtn.setTextColourHex(this?.textModel?.primaryColor)
                mainCv.setCardBackgroundColourHex(this?.interfaceModel?.secondaryColor)
                roleCv.setCardBackgroundColourHex(item?.roleColour)

                // Check if course is CPD and display CPD icon
                if (item?.isCPD == true) cpdLogo.visibility =
                    View.VISIBLE else cpdLogo.visibility = View.INVISIBLE
            }
        }
    }
}

interface Interaction {
    fun onItemSelected(
        tag: String,
        position: Int,
        result: ResultResponseModel?
    )
}
}

Upvotes: 4

Views: 1740

Answers (2)

SpiritCrusher
SpiritCrusher

Reputation: 21053

Its the notifyItemMovewhich is causing this scrolling. Somehow LinearLayoutManager causes List to scroll if 0th position is changed .

For your case 0th position is header which will be at same position regardless of sorting Order .

What you can do is keep the 0th item and Sort rest of the part inside your filterList method. I have tried it on sample and it worked for me. Here is a post which explains the similar thing.

"courseName" -> {
                val updatedList = arrayListOf<ResultResponseModel>()
                updatedList.add(list[0])
                updatedList.addAll(list.subList(1,list.size).sortedBy { it.courseName })
                filteredList = updatedList
            }

This is just pseudo code to explain the idea. Give it a try .

Upvotes: 0

Hussien Fahmy
Hussien Fahmy

Reputation: 1295

register the adapter to observe to notify you when the data changes using registerAdapterDataObserver adapter.registerAdapterDataObserver() and when the data changed scroll the recyclerView to the first item position using recyclerview.scrollToPosition(position)

see also: Recycler view scroll to specific position and Diffutil in recycleview, making it autoscroll if a new item is added

Upvotes: 0

Related Questions