안드로이드

[Kotlin/Android] RecyclerView (2) DiffUtil & ListAdapter

란서 2021. 12. 31. 12:20

DiffUtil & ListAdapter

 

  • DiffUtil 탄생(?)배경

     이전에 리사이클러뷰가 보여주는 data 목록에 (추가, 삭제, 변경) 이 발생하면 notifyDataSetChanged() 함수를 사용하여 리사이클러뷰에 알리도록 했다. 

    val data = listOf<SleepNight>()
    	set(value) {
        	field = value
    		notifyDataSetChanged()
        }​
     
     하지만 해당 함수를 사용한 데이터 갱신은 리사이클러뷰로 하여금 모든 아이템들을(아직 스크린에 보여지지 않는 부분 포함)모두 갱신하게 만들어 Large한 데이터 목록을 다룰 경우 비효율적이라는 문제점이 있다.

     따라서 두 리스트(old , new) 를비교하여 데이터 목록의 추가, 제거, 변경 상태를 해당 데이터만 리사이클러 뷰에 알려주는 DiffUtil가 탄생하게 되었다. 


  • DiffUtil 사용

    Adapter class가 있는 파일에 새로운 Top-Level 클래스
    DiffUtilCallback : DiffUtilCallback<data class 타입>를 만든다.

    class SleepNightDiffUtilCallback : DiffUtil.ItemCallback<SleepNight>() {
    	override fun areItemTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        	return oldItem.nightId == newItem.nightId
        }
        
    	override fun areContentsTheSame(oldItem: SleepNight, newItem:SleepNight) : Boolean {
        	return oldItem == newItem
        }
    }​


    - override fun areItemTheSame(~~): Boolean => oldItem의 유니크한 변수와 newItem의 유니크한 변수 (nightId)를 비교한다. 해당 함수는 데이터의 추가 및 제거를 확인하는 함수.

    -override fun areContentsTheSame(~~): Boolean -> oldItem과 newItem이 가진 모든 프로퍼티를 비교한다. data class는 equals을 비교할 수 있는 함수를 기본적으로 제공하기 때문에 가능. 해당 함수는 데이터의 업데이트를 확인하는 함수.


  • ListAdapter 사용

    DiffUtil class를 사용하기 위해서는 기존의 Adapter class의 상속을 ListAdatper로 바꾸어 줘야 한다. 
    ListAdapter는 RecyclerView에서 지원하는 Adapter의 종류 중 하나이다.

    ListAdapter<Data class type, ViewHolder class>(DiffUtil class)
    //기존 Adapter 클래스
    class sleepNightAdapter : RecyclerView.Adapter<sleepNightAdatper.ViewHolder>(){}
    에서

    //변경된 Adapter 클래스 
    class sleepNightAdapter 
    : ListAdatper<SleepNight, sleepNightAdapter.ViewHolder>(SleepNightDiffUtilCallback()){ }​

    로 변경.

    변경됨에 따라 기존 Adatper 클래스 내에 있던 변수와 함수들이 불필요하게 되는데,

    val data = listOf<SleepNight>() 
    
    override fun getItemCount() : Int = data.size

    해당 프로퍼티와 변수는 사라지게 된다. 

    따라서 해당 프로퍼티를 사용하던 onBindViewHolder 함수 또한 변경.

    override fun onBindViewHolder(holder: ViewHolder, position:Int) {
    	//val item = data[position] 
        val item = getItem(position) 
        ...
    }​

    또한 Fragment내에서 adapter에 데이터를 초기화하는 코드 또한 변경.

    //In SleepNightTrackerFragment.kt 
    viewModel.nights.observe(viewLifecycleOwner, Observer{ 
    	it?.let{ adatper.submitList(it) } 
    })​
    adatper . submitList(data: SleepNight) 함수를 통해 새로운 데이터로 초기화 할 수 있다.