4

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?
    )
}
}
AndroidDev123
  • 280
  • 3
  • 24
  • Your image website is keeping the image private. You can paste the image directly into your question, which will upload it to Imgur, and someone can update it to make it visible in the body of your question. – Tenfour04 Dec 16 '21 at 19:01
  • Its now embedded in the question – AndroidDev123 Dec 17 '21 at 08:36
  • you can try to set new adapter again instead of notify or update. So when you select any new chip adapter will reset again. – pratik vekariya Dec 23 '21 at 06:30
  • Thats what was happening before and it works, its just not effective as its forcing a redraw of everything on screen – AndroidDev123 Dec 23 '21 at 09:34

2 Answers2

0

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

Hussien Fahmy
  • 1,119
  • 6
  • 24
  • Thank you for your answer, I already did this before asking my question but its not the desired functionality. the position should never move from 0 regardless of the diff util updating the list – AndroidDev123 Dec 21 '21 at 18:03
0

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 .

ADM
  • 20,406
  • 11
  • 52
  • 83