버그 잡이

(AAC 응용) LiveData+Room+RecyclerView #DiffUtil 본문

모던 안드로이드/Udacity Android with kotlin

(AAC 응용) LiveData+Room+RecyclerView #DiffUtil

버그잡이 2020. 4. 15. 14:23

기본 리사이클러뷰 만들기는 저번 글 참고

https://jinsangjin.tistory.com/24

 

[코틀린] Kotlin RecyclerView 각 요소 살펴보기

지금까지는 안드로이드를 공부할때 기능구현에만 중점을 두었다. 이제부터는 각 기능의 요소를 한줄한줄 파헤치며 공부해보고자 한다. (recyclerView 튜토리얼이 아니라 recyclerView 자체에 대한 이해를 높이고자..

jinsangjin.tistory.com

 

 

LiveData + Room 적용시 Issue

 

1. LiveData로 감싸진  Room 데이터를 todolist.value처럼 그냥 사용하려면 NPE 발생

     viewModel.todoList.let {
            it.value?.let{
                viewAdapter = TodoListAdapter(it)

                todoRecyclerView.setHasFixedSize(true)
                todoRecyclerView.adapter = viewAdapter
            }
            it.observe(this, Observer {
                viewAdapter.notifyDataSetChanged()
            })
        }

 

// *todoList : LiveData<List<TodoData>>

 

위와 같이 liveData의 value값만 받아서 넣으려는데 자꾸 NPE 뜬다.

왜 그런지 알아보니 LiveData로 감싸진 room데이터는 observer 안에서만 받을 수 있단다.

roomDB는 메인스레드에서 접근할 수 없어서 그런 것이라고 한다.

https://stackoverflow.com/questions/44428389/livedata-getvalue-returns-null-with-room

 

(수정 후)

viewModel.todoList.observe(this, Observer {
    val viewAdapter = TodoListAdapter(it)
    binding.todoRecyclerView.adapter = viewAdapter
    viewAdapter.notifyDataSetChanged()
          
})

 

 

 

2. 위와 같이 observer에서 데이터를 받을 경우 viewAdapter를 매번 최신화 해야하는 문제

 

-> Adapter에서 list 받는 것을 constructor가 아닌 setter로 바꿔준다.

 

*TodoListAdapter

- adapter에서 constructor로 받는 변수를 지우고 다음과 같은 setter를 생성한다.

    var list = listOf<TodoData>()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

 

*MainActivity

   val viewAdapter = TodoListAdapter()
   binding.todoRecyclerView.adapter = viewAdapter

   viewModel.todoList.observe(this, Observer {
       viewAdapter.list = it
       viewAdapter.notifyDataSetChanged()
   })

 

 

 

DiffUtil의 활용

 

- notifyDataSetChanged는 리스트를 전부 최신화 하기 때문에 expensive한 작업이다.

 

- 이런 경우 DiffUtil을 사용하면 보다 효율적으로 데이터를 최신화할 수 있다.

 (기존 데이터와 새로운 데이터를 비교해서 달라진 부분만 수정하는 그런 알고리즘이 담겨 있다고 한다.)

 

* DiffUtil 은 DB를 바탕으로 계산하기 때문에 계산 대상이 Room data class가 아니라 그냥 data class라면 계산이 적용되지 않는다.

 

 

1. DiffCallback 클래스 생성

 

*areItemTheSame은 말그대로 item이 추가되거나 삭제된 것이 있는지 확인

*areContentsTheSame은 itemId는 같지만 그 세부사항이 변경된 것이 있는지 확인. 특정 항목의 변화만 확인하고 싶으면 커스텀 해서 작성할 수 있다.

이 두가지 필터로 걸린 항목들을 최신화 하는 것이다.

class TodoListDiffCallback : DiffUtil.ItemCallback<TodoData>(){
    override fun areItemsTheSame(oldItem: TodoData, newItem: TodoData): Boolean {
        return oldItem.todoId == newItem.todoId
    }

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

}

 

2. Adapter 상속을 기존 recyclerView에서 listAdapter로 변경

 

- ListAdapter를 아래와 같이 상속 받는다.

class TodoListAdapter(): ListAdapter<TodoData, TodoListViewHolder>(TodoListDiffCallback()){

//    var list = listOf<TodoData>()
//        set(value) {
//            field = value
//            notifyDataSetChanged()
//        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoListViewHolder {
        val todoItem = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_todo, parent, false)

        return TodoListViewHolder(todoItem)
    }

    override fun onBindViewHolder(holder: TodoListViewHolder, position: Int) {
        holder.containerView.todoContent.text = getItem(position).todoContent
    }

    //override fun getItemCount(): Int = list.size ?: 0

}

- 그 결과 setter, getItemCount는 필요가 없어진다.(ListAdapter에서 관련 기능을 제공)

 

 

*MainActivity.kt

val viewAdapter = TodoListAdapter()
binding.todoRecyclerView.adapter = view

viewModel.todoList.observe(this, Observer {
    viewAdapter.submitList(it)  //기존 viewAdapter.list = it 에서 다음과 같이 수정
})

 

반응형
Comments