[개념] 리사이클러뷰와 리스트 어댑터의 DiffUtil

1. Adapter의 역할.

Adapter가 맡은 역할은 크게 아래의 네 가지로 나눌 수 있습니다.

  1. xml의 마크업 코드를 객체로 inflate를 할 때, ViewHolder에 View 객체를 재사용.
  2. 화면에 RecyclerView를 통해 출력할 데이터(dataSet) 관리. ex) Data index 접근 및 data가 출력될 ViewHolder 변경 등이 가능하다.
  3. 경우에 따라 Click Event 관리.
  4. Adapter는 ListView와 RecyclerView의 position에 해당하는 데이터를 View에 Bind 하여 출력합니다.

2. 리사이클러뷰와 리스트 어댑터의 DiffUtil의 사용시 차이점.

RecyclerView (setHasStableIds)

3. DiffUtil.ItemCallback 분석하기

abstract class BaseModel(
    open val id : Long,
    open val type : CellType
) {

    open fun isTheSame(item: BaseModel) : Boolean {
        return this.id == item.id && this.type == item.type
    }

    companion object {
        val DIFF_CALLBACK = object: DiffUtil.ItemCallback<BaseModel>() {
            override fun areItemsTheSame(oldItem: BaseModel, newItem: BaseModel): Boolean {
                return oldItem.isTheSame(newItem)
            }

            @SuppressLint("DiffUtilEquals")
            override fun areContentsTheSame(oldItem: BaseModel, newItem: BaseModel): Boolean {
                return oldItem == newItem
            }
        }
    }
}

위 코드에서 과거(oldItem)와 현재(newItem) 리스트의 차이를 비교 해서 areItemsTheSame과 areContentsTheSame를 override 해주는 것을 확인 할 수 있습니다.

아래의 그림으로 각 함수의 사용을 설명하겠습니다.

    open fun isTheSame(item: BaseModel) : Boolean {
        return this.id == item.id && this.type == item.type
    }
    
    override fun areItemsTheSame(oldItem: BaseModel, newItem: BaseModel): Boolean {
                return oldItem.isTheSame(newItem)
    }
            

areItemsTheSame은 위와 같이 id 값과 같은 고유값을 비교해서 같은 id인지 확인을 합니다. 이를 통해 이전 항목과 같은 것 인지 테스트를 하게 됩니다. 같으면 true를, 아니면 false를 리턴합니다.

여기서 areItemsTheSame에 false가 나오게되면 false가 나온 항목의 viewholder 를 다시 그려주게 됩니다. 그래서 깜빡거리는 현상이 나타나게 됩니다. true를 반환시에는 깜빡거리는 현상이 나타나지 않습니다.

false 의 경우 예시)

위의 예시는 전체가 깜빡이지만, areItemsTheSame은 항목 id 각각을 비교하여 flase를 반환한 경우 일치하지 않는 id만 viewholder부터 다시 그리고 안의 내용이 작성되게 됩니다. 그래서 true를 반환하는 id의 경우는 깜빡거리지 않습니다.

    @SuppressLint("DiffUtilEquals")
    override fun areContentsTheSame(oldItem: BaseModel, newItem: BaseModel): Boolean {
        return oldItem == newItem
    }

areContentsTheSame 은 areItemsTheSame true일 경우만 호출이 됩니다. 그래서 true가 반환이 된다면 viewholder는 다시 그리지 않기 때문에 깜빡거리는 것이 없게 data class 내부의 값 전체를 다 비교합니다. 비교 후 바뀐 Contents 값을 갱신 해줍니다.

true 의 경우 예시)

4. Team Project Yu Maket 적용 사례

테스트 mock data 2개의 List를 각각 준비합니다.

    override fun getAllMarketList(): List<TownMarketModel> {
        val mockList = listOf(
            TownMarketModel(
                id = 0, "쥬얼리 샵", true,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.11f
            ),
            TownMarketModel(
                id = 1, "옷가게", true,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.22f
            ),
            TownMarketModel(
                id = 2, "피자스쿨", true,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.33f
            ),
            TownMarketModel(
                id = 3, "빅마트", false,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.44f
            ),
            TownMarketModel(
                id = 4, "롯데리아", false,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.10f
            )
        )

        return mockList
    }
override fun getAllMarketListtest(): List<TownMarketModel> {
        val mockList = listOf(
            TownMarketModel(
                id = 0, "쥬얼리 샵", true,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.11f
            ),
            TownMarketModel(
                id = 1, "옷가게", true,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.22f
            ),
            TownMarketModel(
                id = 2, "피자스쿨(test)", true,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.33f
            ),
            TownMarketModel(
                id = 5, "빅마트(test)", false,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.44f
            ),
            TownMarketModel(
                id = 4, "롯데리아(test)", false,
                LocationLatLngEntity(128.0, 36.0),
                "https://picsum.photos/200", 0.10f
            )
        )

        return mockList
    }

각 데이터들은 실험을 위해서 서로의 차이를 둬서(test)를 붙였습니다. 빅마트의 경우만 id 값이 3->5로 변경되었고 나머지는 id값의 변경이 없습니다. 옷가게와 쥬얼리 샵은 test를 붙이지 않아서 데이터의 변경이 일어나지 않게 하였습니다.

위와 같이 데이터 값을 정해놓고 areContentsTheSame 과 areItemsTheSame 을 어떻게 이해하고 사용하였는지 확인 해보겠습니다.

id값의 변환이 있기 때문에 뷰홀더를 다시 그려서 빅마트의 경우만 크게 깜빡거리는 것을 확인할 수 있습니다. areContentsTheSame에서 false를 반환했기 때문이죠. 나머지 경우 피자스쿨의 경우 살짝 반짝이는 경우를 알 수 있는데 제목이 test가 붙어서 변했기 때문에 뷰홀더를 다시 그리지는 않고 내부의 데이터값만 변환 되는 것을 알 수 있습니다.

5. reference

https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil
https://developer.android.com/reference/androidx/recyclerview/widget/ListAdapter
https://developer.android.com/reference/androidx/recyclerview/widget/AsyncListDiffer

https://jaeryo2357.tistory.com/70
https://velog.io/@ilil1/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Yu-Market-%EC%B2%AB%EB%B2%88%EC%A7%B8-%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-2%ED%8E%B8

좋은 웹페이지 즐겨찾기