ListAdapter 를 사용 중 UI 업데이트가 되지 않는 현상 | 삽질 노트

hoya·2022년 2월 4일
1

삽질 노트

목록 보기
4/8
post-thumbnail

😡 문제 상황

ListAdapter를 사용 중 submitList()를 통해 새로운 데이터를 넣어도 실시간 데이터 업데이트가 되지 않는다.


🤔 원인 파악

기본적으로 DiffUtillareItemsTheSame 에서 true를 반환해야 areContentsTheSame 메소드를 실행하고, 거기서 false를 반환하면 데이터가 변경되었다고 판단, UI 업데이트가 이루어진다.

이를 활용하여, oldItem 의 내용과 newItem 의 내용을 로그에 담아 비교해보기로 했다. 코드는 아래와 같다.


        val myDiffCallBack = object : DiffUtil.ItemCallback<TaskItem>() {
          
            override fun areItemsTheSame(oldItem: TaskItem, newItem: TaskItem): Boolean {
                Log.d(TAG, "areItemsTheSame: ${oldItem.task.id == newItem.task.id}")
                return oldItem.task.id == newItem.task.id
            }
            
            override fun areContentsTheSame(oldItem: TaskItem, newItem: TaskItem): Boolean {
                Log.d(TAG, "areContentsTheSame: ${oldItem.task.content} : ${newItem.task.content} + ${oldItem == newItem}")
                return oldItem == newItem
            }
        }

그리고 결과가 위와 같이 나왔다. 원인은, oldItem 의 값에 새로 넣은 값, 즉 newItem 의 값이 들어가며 결과가 true 가 반환되는 것이 원인이었다.

원래대로라면, oldItem 의 값에는 321321456 이 들어가야 하는 것이 맞다.


😎 해결

StackOverFlow 에서 해결책을 여러가지로 찾아보았으나, 결론부터 말하자면 완전한 해결책은 없었다. 😱

그나마 이해가 되는 답변은 아래와 같다.

  • DiffUtil stores the old items by reference - it doesn't copy them. Your SubTask class is mutable, so when you call setCompleted, you're changing the old one that DiffUtil has a reference to as well. DiffUtil is only able to compare to a list of old, unchanged objects. Because you changed the old object, and it only has a reference to the object that you've now changed, it doesn't know the correct old value. -> (References : StackOverFlow)

DiffUtill 은 기본적으로 oldItem 을 복사하는 것이 아니라, 참조하여 저장한다. oldItemMutable, 즉 변경 가능한 객체라면 데이터가 변경될 때 oldItem 도 변경되어 올바른 비교가 불가능한다는 답변이다.

이에 대해 완전한 해결책은 찾지 못했으나, 성능 저하가 우려되지만 어느정도 오류를 해결할 수 있는 방법을 찾았다.


우선 첫번째 방법은 아래와 같다. (References : StackOverFlow)

        listViewModel.tasks.observe(viewLifecycleOwner) {
            taskListAdapter.submitList(null)
            taskListAdapter.submitList(it.toMutableList())
        }

처음 전달 값에 아예 null을 줘버리고, 그 다음에 변경된 리스트를 주는 것인데, 사실 이러면 전체 값이 변경되므로 ListAdapter 를 쓰는 이유가 없다. 심지어, 변경 후엔 화면 깜빡임까지 발생한다.

그리고 여러 방안을 시도해봤으나 해결이 되지 않던 와중, 그나마 타협할 수 있는 방법을 발견했다.

바로, areContentsTheSame 메소드에서 리턴값을 전부 false 로 줘버리는 것이다. [..]

이게 맞는 방법인지는 잘 모르겠지만, 일단 데이터 변경을 감지하면 변경된 데이터를 넘기는 식으로 코드가 구성되어 있기 때문에 이 방법을 채택했다.

        val myDiffCallBack = object : DiffUtil.ItemCallback<TaskItem>() {
        
            override fun areItemsTheSame(oldItem: TaskItem, newItem: TaskItem): Boolean {
                return oldItem.task.id == newItem.task.id
            }
            override fun areContentsTheSame(oldItem: TaskItem, newItem: TaskItem): Boolean {
                return false
            }
        }

일단 기대한대로 결과는 나오기는 하지만, 완전한 해결책은 아니라고 생각한다. 어느정도 봉합을 했다는 것에 만족하도록 하자.

혹시 이 글을 보고 해결책을 아시는 분이 계시다면 공유 부탁드립니다 😰

profile
즐겁게 하자 🤭

0개의 댓글