0

it's my first post in stack overflow, sorry in advance if my message has charter faults.

I've problem using DiffUtil in my project, i hope you can help me. I explain:

I build an app who manage different students class. I have fragment who display a students list in a recyclerView. I recove this list from a local bdd. My aim is when i scroll the recyclerView to display the items at the bottom (at the base don't displayed) and i click on interactive element on item (student), the fragment refresh the list and go to the top of the list. I've decided to use DiffUtil to fix this problem because i want to modify items many time anywhere in the list. I've solve it but an other problem was came. When i switch student class, list A to list B, (and switch student list) i want display other student list and Diffutil compare the newList (list B) with the old list (list A) and no old list and new list based from the list B. I've tried many solutions but it was inconclusive...

Here my Fragment :

class ClassManagementFragment : Fragment(), StatsUpdater {

private lateinit var binding: FragmentClassManagementBinding
lateinit var adapter: StudentsListAdapter
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
private var currentClass: StudentsClass? = null
private var experienceGiven: Int = 1
private var mActionMode: ActionMode? = null
private var myStudentsList: List<Student> = emptyList()

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!!)
        databaseCallsVM.getClassInformation(classID!!)
            ?.observe(requireActivity()) { cClass ->
                currentClass = cClass
            }
    } 
    updateExperienceGiven(experienceGiven)
    return binding.root
}

override fun onResume() {
    super.onResume()
    if (currentClass != null) activity?.title = currentClass!!.name
}

private fun configureRecyclerView(classDisplayed: Int) {
    //val recyclerView = binding.studentsListRecyclerView
    adapter = StudentsListAdapter(this@ClassManagementFragment, uiConfigure)
    recyclerView.adapter = adapter
    recyclerView.layoutManager = LinearLayoutManager(activity)
    recyclerView.addItemDecoration(
        DividerItemDecoration(
            requireActivity(),
            DividerItemDecoration.VERTICAL
        )
    )


    databaseCallsVM.getAllStudentsInClass(classDisplayed)
        ?.observe(requireActivity()) {
            //val firstStudentName = it[0].students[0].firstName
            if (it.isNotEmpty()) {
                val students = it[0].students
                students.let { studentsList ->
                    val studentListSorted = studentsList.sortedBy { student ->
                        student.lastName
                    }
                    adapter.submitList(studentListSorted.toList())
                }
            }
        }
}

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)
}

override fun openDetail(student: Student) {
    val intent = Intent(requireActivity(), StudentDetailsActivity::class.java)
    intent.putExtra("student", student)
    intent.putExtra("classId", classID)
    startActivity(intent)
}

private fun updateExperienceGiven(xpChoosed: Int) {
    adapter.updateExperienceGiven(xpChoosed)
}
}    

And here my 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 StudentsListAdapter(private val upStats: StatsUpdater, private val uiConfigure: UiConfigure) :
ListAdapter<Student, StudentsListAdapter.StudentViewHolder>(
    AsyncDifferConfig.Builder<Student>(
        DifferCallback()
    ).build()
) {

private lateinit var binding: ItemStudentBinding
var giveExperience: Int = 1

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

fun updateExperienceGiven(xpChoosed: Int) {
    this.giveExperience = xpChoosed
}

inner class StudentViewHolder(private val itemBinding: ItemStudentBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun setData(item: Student) {
        binding.apply {
            // XP & LVL
            val xpBar = this.studentXpBar
            uiConfigure.displayExperience(
                item.experience,
                item.xpMax,
                this.studentLevelCurrentXp
            )
            studentLevelResponse.text = item.level.toString()
            uiConfigure.updateProgressBarXp(
                item.experience,
                item.xpMax,
                giveExperience,
                this.studentXpBar
            )
            xpBar.progress = item.experience
            xpBar.max = item.xpMax
            xpBar.setOnClickListener {
                xpBar.progress += giveExperience
                if (xpBar.progress >= xpBar.max) {
                    val currentXp = (item.experience + giveExperience) - xpBar.max
                    val newXpMax = xpBar.max + 5
                    upStats.updateExperience(item, currentXp, newXpMax)
                    upStats.updateLevel(item)
                } else {
                    upStats.updateExperience(item, xpBar.progress, xpBar.max)
                }
            }
            // LIFE
            uiConfigure.displayHeartIconLife(item.pointOfLife, this.studentLifeImage)
            uiConfigure.displayLifeNumber(item.pointOfLife, this.studentLifePoint)
            this.studentLifeImage.setOnClickListener {
                var currentLife = item.pointOfLife
                if (item.pointOfLife == 0) {
                    currentLife = 3
                } else currentLife--

                upStats.updateLife(item, currentLife)
            }

            // NAME
            this.studentFirstname.text = item.firstName
            this.studentLastname.text = item.lastName

            // GROUP
            this.studentIlotNumber.text = item.group.toString()
            var groupNumber = item.group
            uiConfigure.changeGroupImageColor(groupNumber, this.studentIlotImage)

            this.studentIlotImage.setOnClickListener {
                if (groupNumber >= 10) groupNumber = 1
                else groupNumber++
                upStats.updateGroup(item, groupNumber)
            }

            // BELT
            uiConfigure.setBelt(item.bestBelt, this.studentBelt)

            // JOB
            uiConfigure.displayJobImage(item.job, this.studentJobImage)

            // Detail
            itemView.setOnClickListener {
                upStats.openDetail(item)
            }
        }
    }

    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.class_id == newItem.class_id && oldItem.pointOfLife == newItem.pointOfLife && oldItem.level == newItem.level &&
                oldItem.experience == newItem.experience && oldItem.group == newItem.group
    }

    override fun getChangePayload(oldItem: Student, newItem: Student): Any? {
        if (oldItem.id == newItem.id) {
            return if (oldItem.class_id == newItem.class_id && oldItem.level == newItem.level && oldItem.experience == newItem.experience &&
                oldItem.pointOfLife == newItem.pointOfLife && oldItem.group == newItem.group
            ) {
                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)
    }
 }
}    

Anyone have an idea what's wrong ?

I've tried to refresh fragment, not work. Same with add if() condition by the student class id but not work. And other attempts but I forgot this.

alea
  • 980
  • 1
  • 13
  • 18
Etheys
  • 1
  • I'm not sure I understand the problem you're facing. Are you saying that when you update to display *list B*, your `DiffUtil` compares that to the old *list A* data, and you don't want that to happen? – cactustictacs Apr 28 '23 at 15:16
  • Yes, I want when I switch list, DiffUtil compares list A with list A, on switch, compares list B with list B and again list C with list C... – Etheys Apr 29 '23 at 18:21
  • `ListAdapter`s take their data through `submitList`, right? So when you do `adapter.submitList(listA)`, its current data is *listA*. If you switch to displaying *listB* with `submitList(listB)`, then it will do the diff compare with the previous data, which is *listA*. It's designed so whenever you update the contents, it always compares with the last contents, like *listA -> listB*. You could try `submitList(emptyList())` to *"clear"* it before submitting *listB*, but that might give a visual glitch. – cactustictacs Apr 29 '23 at 19:07
  • Honestly you might just want to use separate adapters for each list? Create an instance for each list, `adapterA`, `adapterB` and so on, the same way you're creating `adapter` now. That way, to display *listB*, you can do `recyclerView.adapter = adapterB` and it'll switch to showing that data. And by doing `adapterB.submitList(listB)`, you're ensuring that adapter will **only** have *listB* data, it will never get *listA* data to compare to, if you see what I mean. (You could have a `currentAdapter` variable to keep your code general, e.g. `current = adapterA`, `current.submitList(listA)` etc) – cactustictacs Apr 29 '23 at 19:14
  • I've don't thought to call multi adapter. Good idea, i go to test that, thanks a lot ! – Etheys May 03 '23 at 06:52

0 Answers0