Showing an element changes on scroll with diff util and recyclerView

52 Views Asked by At

i'm using recyclerView with DiffUtil and i encounter a problem that I can't solve. When i display my students list, the 17th item change when i'm scrolling and below this element, next items match first items in my list. But on click at 1 item (example the 19th items), data correspond 19th items but the list display 2th items in the view. I want when I’m clicking on image (heart, experience, group), data change on my database and I see modification in real time without loading my list because when I change all items 1 by 1, scrolling view, my recyclerView display again start of the list (goes back to the top of the list and I have to go down again to access the elements below). The reason why I want to use Diff Util library.

You can see the problem on the images below. After the student named T.T, we can see, as we scroll, the next student's name and other information changes. After it, the recycler view displays the beginning of the list. [Link to application images]

(https://i.stack.imgur.com/WIhjd.png) (https://i.stack.imgur.com/hPobt.png) (https://i.stack.imgur.com/VjL3T.png)

Here is my code : Here my model (i know my variables are in var because i want to modify and update their in my database) :

data class Student(
    val class_id: Int,
    @ColumnInfo(name = "first_name") var firstName: String,
    @ColumnInfo(name = "last_name") var lastName: String,
    var job: String,
    @ColumnInfo(name = "point_of_life") var pointOfLife: Int,
    var level: Int,
    var experience: Int,
    var xpMax: Int,
    var group: Int,
    var bestBelt: Int,
    var beltXp: Int,
    var currentBelt: Int,
    var numNinjaXp: Int,
    var xpAndPowersFrozen: Long): Serializable {
    @PrimaryKey(autoGenerate = true)
    var id: Int = Random.nextInt()
}

Here my fragment where i display my recyclerView :

class ClassManagementFragment : Fragment(), StatsUpdater {

    private lateinit var binding: FragmentClassManagementBinding
    private lateinit var firstAdapter: StudentsListOneAdapter
    private lateinit var secondAdapter: StudentsListTwoAdapter
    private lateinit var thirdAdapter: StudentsListThreeAdapter
    private lateinit var fourthAdapter: StudentsListFourAdapter
    private lateinit var fifthAdapter: StudentsListFiveAdapter
    private lateinit var recyclerView: RecyclerView
    private val databaseCallsVM: DatabaseCallsViewModel by viewModels {
        MathWorldViewModelFactory((requireActivity().application
                as MathWorldApplication).repository)
    }
    private lateinit var mainVM: MainViewModel
    private val uiConfigure: UiConfigure = UiConfigureImpl()
    private var classID: Int? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentClassManagementBinding.inflate(inflater, container, false)
        mainVM = ViewModelProvider(requireActivity())[MainViewModel::class.java]
        setHasOptionsMenu(true)
        recyclerView = binding.studentsListRecyclerView

        mainVM.classNumber.observe(requireActivity()) { id ->
            classID = id
            configureRecyclerView(classID!!)
        }
        return binding.root
    }

    private fun configureRecyclerView(classDisplayed: Int) {
        when(classDisplayed){
            1 -> {
                firstAdapter = StudentsListOneAdapter(this@ClassManagementFragment, uiConfigure)
                recyclerView.adapter = firstAdapter
            }
            2 -> {
                secondAdapter = StudentsListTwoAdapter(this@ClassManagementFragment, uiConfigure)
                recyclerView.adapter = secondAdapter
            }
            3 -> {
                thirdAdapter = StudentsListThreeAdapter(this@ClassManagementFragment,
                    uiConfigure)
                recyclerView.adapter = thirdAdapter
            }
            4 -> {
                fourthAdapter = StudentsListFourAdapter(this@ClassManagementFragment,
                    uiConfigure)
                recyclerView.adapter = fourthAdapter
            }
            5 -> {
                fifthAdapter = StudentsListFiveAdapter(this@ClassManagementFragment, uiConfigure)
                recyclerView.adapter = fifthAdapter
            }
        }
        recyclerView.layoutManager = LinearLayoutManager(activity)
        recyclerView.addItemDecoration(
            DividerItemDecoration(
                requireActivity(),
                DividerItemDecoration.VERTICAL
            )
        )

        databaseCallsVM.getAllStudentsInClass(classDisplayed)
            ?.observe(requireActivity()) {
                if (it.isNotEmpty()) {
                    val students = it[0].students
                    students.let { studentsList ->
                        val studentListSorted = studentsList.sortedBy { student ->
                            student.lastName
                        }
                        when(classDisplayed){
                            1 -> firstAdapter.submitList(studentListSorted)
                            2 -> secondAdapter.submitList(studentListSorted)
                            3 -> thirdAdapter.submitList(studentListSorted)
                            4 -> fourthAdapter.submitList(studentListSorted)
                            5 -> fifthAdapter.submitList(studentListSorted)
                        }
                    }
                }
            }
    }

    override fun updateExperience(student: Student, experience: Int,
                                  xpMax: Int) {
        student.experience = experience
        if (student.xpMax != xpMax) {
            student.xpMax = xpMax
        }
        databaseCallsVM.updateStudent(student)
    }

    override fun updateLevel(student: Student) {
        student.level += 1
        val text = getString(R.string.level_up)
        Toast.makeText(requireActivity(), student.firstName + " " + text,
            Toast.LENGTH_SHORT).show()
        databaseCallsVM.updateStudent(student)
    }

    override fun updateLife(student: Student, life: Int) {
        student.pointOfLife = life
        databaseCallsVM.updateStudent(student)
    }

    override fun updateGroup(student: Student, group: Int) {
        student.group = group
        databaseCallsVM.updateStudent(student)
    }
}

The adapter :

private const val STUDENT_CLASS = "student.class"
private const val STUDENT_LIFE = "student.life"
private const val STUDENT_LEVEL = "student.level"
private const val STUDENT_EXPERIENCE = "student.experience"
private const val STUDENT_MAX_EXPERIENCE = "student.maxExperience"
private const val STUDENT_GROUP = "student.group"

class StudentsListOneAdapter(private val upStats: StatsUpdater,
                             private val uiConfigure: UiConfigure) :
    ListAdapter<Student, StudentsListOneAdapter.StudentViewHolder>(
        AsyncDifferConfig.Builder<Student>(
            DifferCallback()
        ).build()
    ) {

    private lateinit var binding: ItemStudentBinding

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

    override fun onBindViewHolder(holder: StudentViewHolder, position: Int) {
        super.onBindViewHolder(holder, position, emptyList())
    }

    override fun onBindViewHolder(holder: StudentViewHolder, position: Int,
                                  payloads: List<Any>) {
        val item = getItem(position)

        if (payloads.isEmpty() || payloads[0] !is Bundle) {
            holder.setData(item)
        } else {
            val bundle = payloads[0] as Bundle
            holder.update(bundle)
        }
    }

    override fun getItemCount() = currentList.size

    inner class StudentViewHolder(private val itemBinding: ItemStudentBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun setData(current: Student) {
            binding.apply {
                // code to display elements
            }
        }

        fun update(bundle: Bundle) {

            if (bundle.containsKey(STUDENT_LIFE)) {
                val life = bundle.getInt(STUDENT_LIFE)
                uiConfigure.displayHeartIconLife(life, itemBinding.studentLifeImage)
                uiConfigure.displayLifeNumber(life, itemBinding.studentLifePoint)
            }
            if (bundle.containsKey(STUDENT_LEVEL)) {
                val level = bundle.getInt(STUDENT_LEVEL)
                itemBinding.studentLevelResponse.text = level.toString()
            }
            if (bundle.containsKey(STUDENT_EXPERIENCE)) {
                val experience = bundle.getInt(STUDENT_EXPERIENCE)
                val maxExp = bundle.getInt(STUDENT_MAX_EXPERIENCE)
                uiConfigure.displayExperience(
                    experience,
                    maxExp,
                    itemBinding.studentLevelCurrentXp
                )
                uiConfigure.updateProgressBarXp(
                    experience,
                    maxExp,
                    giveExperience,
                    itemBinding.studentXpBar
                )
            }
            if (bundle.containsKey(STUDENT_GROUP)) {
                val group = bundle.getInt(STUDENT_GROUP)
                itemBinding.studentIlotNumber.text = group.toString()
                uiConfigure.changeGroupImageColor(group, itemBinding.studentIlotImage)
            }

        }
    }

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

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

        override fun getChangePayload(oldItem: Student, newItem: Student): Any? {
            if (oldItem.id == newItem.id) {
                return if (oldItem.class_id == newItem.class_id &&
                    oldItem.pointOfLife == newItem.pointOfLife &&
                    oldItem.level == newItem.level &&
                    oldItem.experience == newItem.experience && oldItem.group == newItem.group &&
                    oldItem.firstName == newItem.firstName &&
                    oldItem.lastName == newItem.lastName
                ) {
                    super.getChangePayload(oldItem, newItem)
                } else {
                    val diff = Bundle()
                    diff.putInt(STUDENT_CLASS, newItem.class_id)
                    diff.putInt(STUDENT_LIFE, newItem.pointOfLife)
                    diff.putInt(STUDENT_LEVEL, newItem.level)
                    diff.putInt(STUDENT_EXPERIENCE, newItem.experience)
                    diff.putInt(STUDENT_MAX_EXPERIENCE, newItem.xpMax)
                    diff.putInt(STUDENT_GROUP, newItem.group)
                    diff
                }
            }
            return super.getChangePayload(oldItem, newItem)
        }
    }
}    

Thank you for your help and time.

0

There are 0 best solutions below