
- 이전 편들을 읽고 오시는 것을 권장드립니다.
- 본 설명은 지난 게시물의 [코드 9]를 기반으로 진행됩니다.
모바일, 웹, 데스크탑을 가리지 않고, 거의 모든 GUI 애플리케이션에서 동일한 형태를 가진 많은 데이터가 나열되어 있는 요소(이하
데이터 세트)를 볼 수 있습니다. 즉 클라이언트 개발자 입장에서, 데이터 세트를 그려야 하는 상황은 필연적으로 생기게 됩니다. 본 게시글에서는 안드로이드 프레임워크, 그 중 View System을 활용하여 어떻게 데이터 세트를 보여줄 수 있는지에 관하여 다룹니다.이 글은 ListView, RecyclerView를 접해보지 않은 사람들이 그 원리, 사용이 필요한 상황, 필요성 등을 이해할 수 있도록 하는 것을 목표로 하고 있습니다. 총 세 편에 걸쳐서 발행될 예정이고, 특정 방식을 사용했을 때 발생하는 문제와 그것을 다른 방식으로 해결하는 모습을 step by step 방식으로 설명할 예정입니다.
대상 독자는 처음 안드로이드 개발을 공부하는 사람이지만, 원활한 이해를 위해 Kotlin 문법과 안드로이드 View, ViewGroup 기초 지식 학습이 선행되어야 합니다.
이번 편에서는 개발자 입장에서 RecyclerView를 더 쉽고 효율적으로 사용할 수 있는 방법을 소개합니다.
[코드 9]를 작업하며, 어댑터에 변경 사항을 구체적으로 알려 더 효율적으로 데이터 세트를 갱신을 할 수 있었습니다. 그 목적으로 데이터 세트 변경의 종류에 따라 각기 다른 notify 관련 메소드를 호출하도록 하였습니다.
class BenefitRecyclerViewAdapter(
private var benefits: List<BenefitListItem>,
) : RecyclerView.Adapter<BenefitScreenRecyclerViewHolder>() {
// ...
fun refreshBenefitItem(position: Int, newBenefits: List<BenefitListItem>) {
benefits = newBenefits
notifyItemChanged(position)
}
fun changeBenefitItemPosition(
prevPosition: Int,
newPosition: Int,
newBenefits: List<BenefitListItem>,
) {
benefits = newBenefits
notifyItemMoved(prevPosition, newPosition)
}
}
현재까지는 두 가지의 변경사항만 존재하기 때문에 두 개의 메소드만 추가하였지만, 더 많은 종류의 변경 사항을 적용해야 할 수도 있을 것입니다.
RecyclerView 측에서 제공하는 notify 관련 메소드는 7가지(notifyDataSetChanged 제외)인데, 각 변경 사항을 일일이 고려하고 사용하기란 쉽지 않은 일입니다. 또한 상황에 따라 두 개 이상의 메소드를 조합해야 할 수도 있을텐데, 그 개수가 많아질 수록 개발자의 실수가 발생할 가능성도 커지고 디버깅도 더욱 어려워질 것입니다.
다행히도 개발의 편의성을 높여주는 DiffUtil, AsyncListDiffer와 ListAdapter가 존재합니다. 이 도구들을 통해 어댑터에 변경 사항을 더욱 쉽게 반영할 수 있습니다. 먼저 DiffUtil에 사용되는 알고리즘을 살펴보도록 하겠습니다.
알고리즘 관련 내용입니다.
궁금하지 않으시다면 넘어가셔도 좋습니다.
두 시퀀스 간 최소 편집 거리를 찾는 효율적인 방법으로, 버전을 관리하는 도구인 Git에서도 사용될 만큼 유명한 알고리즘입니다.
변경 전 및 변경 후 데이터를 두 축으로 하는 2차원 그래프를 만들고, 경로 탐색을 통해 최소 편집 거리를 찾는 방식입니다...
라는 설명은 혼란만 가중시키니, 직접 간단한 예시를 들어보도록 하겠습니다.
RecyclerView에 띄울 원본 데이터 세트를 [a, b, c, d, e]에서 [a, b, d, c, e]로 변경한다고 가정해봅니다.
먼저 2차원 배열을 하나 그리는데, (이전 데이터 세트 크기) * (변경 후 데이터 세트 크기) 사이즈의 배열을 준비합니다. 여기서는 데이터 세트의 크기가 변경 전, 후 동일하게 5이므로, 5 * 5 규모의 배열을 준비합니다.
x축에 원본 데이터 세트 아이템을, y축에는 변경된 데이터 세트 아이템을 순서대로 나열합니다.

이후 각 칸을 모두 순회하며 x축 아이템과 y축 아이템이 동일하다면, 칸에 대각선(diagonal line)을 표시합니다. 해당 대각선은 추가적으로 이동할 수 있는 경로를 의미합니다.

이제 편의 상 최북서단의 좌표를 (0, 0), 최남동단 좌표를 (5, 5)라고 가정하겠습니다. 이제 (0, 0)에서부터 (5, 5)까지의 최단 경로를 탐색하는 것을 목표로 합니다.

본격적으로 이동을 진행해보겠습니다.
여러 절차들은 생략하고, 최소 비용으로 이동할 수 있는 경로들을 나타내 보겠습니다.

총 최단 경로는 6가지로, 모든 경로들을 나열해보겠습니다.
(0, 0) -> (1, 1) -> (2, 2) -> <4개 변경 지점> -> (5, 5)는 공통 경로입니다.
(자세한 원리는 다른 게시글을 찾아보시길 바랍니다. 개인적으로는 이 게시물이 이해가 잘 되었습니다.)
이 경로들 중 하나를 활용하여, 문자열 변경 사항을 추적해보도록 하겠습니다.
(0, 0)→(1, 1)→(2, 2)→(2, 3)→(3, 3)→(3, 4)→(4, 4)→(5, 5)
먼저, 이동 방향을 나열해보겠습니다.
대각선→대각선→오른쪽→아래→오른쪽→아래→대각선
한 꼭짓점에서 다음 꼭짓점으로 이동할 때 2가지(대각선이 없을 때) 혹은 3가지(대각선이 있을 때) 방법으로 이동할 수 있었습니다. 각 이동의 의미는 다음과 같습니다.
아래 : 변경 후 문자열의 현재 위치 문자를 변경 전 문자열의 현재 위치로 삽입함을 의미합니다. 예를 들어, (0,0)에서 (1,0)로 이동하는 것은 변경 전 문자열에 변경 후 문자열의 첫 문자인 'A'를 삽입하는 것을 의미합니다.
오른쪽 : 변경 전 문자열의 현재 위치 문자를 변경 전 문자열의 현재 위치에서 삭제함을 의미합니다. 예를 들어, (0,0)에서 (0,1)으로 이동하는 것은 변경 전 문자열의 첫 번째 문자인 'A' 문자를 삭제하는 것을 의미합니다.
대각선 : 해당 문자를 그대로 유지하는 것입니다. 대각선은 동일한 문자에 해당하는 칸에 배치되어 있습니다.
한 가지 유념해야 할 점은 현재 예시를 기준으로 좌표 숫자의 의미는 (변경 후 문자열의 포지션, 변경 전 문자열의 포지션)을 의미한다는 것입니다. 자세한 이동 방식을 적용한 예시는 다음과 같습니다.
ABCDE vs ABDCE
(2, 2) → (3, 2) 이동 방법 예시
아래쪽 이동.
현재 AB까지는 작업이 끝난 상태로, 이미 대각선 2회 이동이 진행 완료
즉 현재까지 상태는 ABCDE vs ABDCE로 아무런 변경도 없는 상태
작업 대상 문자: 변경 후 문자열의 세 번째 문자
(앞 숫자 좌표가 변경되었음. 즉 변경 후 문자열 포지션이 2 -> 3으로 이동했기에,
변경 후 문자열의 세 번째 문자열인 D가 타겟)
아래쪽 이동이 삽입인 이유:
변경 후 문자열의 세 번째 자리에 D가 있지만,
변경 전 문자열의 세 번째 자리에는 D가 없기 때문에 변경 전 문자열의 세 번째 자리에 새로 삽입
예시)
S1 = "ABCDE" -> "ABDCDE", S2 = "ABDCE"
어떻게 이동하는지 알게 되었으니, 본격적으로 변경 사항을 추적해보겠습니다.
변경 전 문자열 ABCDE를 기준으로, 다음과 같은 작업을 수행합니다.
① 대각선 : 그대로 다음 문자열로 이동합니다. (ABCDE -> ABCDE)
② 대각선 : 그대로 다음 문자열로 이동합니다. (ABCDE -> ABCDE)
③ 오른쪽 :(2, 2)→(2, 3)로, 변경 전 문자열의 세 번째 문자가 대상이 됩니다(2 -> 3이므로). 변경 전 문자열 ABCDE에서 세 번째 문자열의 문자(C)를 현재 위치에서 삭제합니다. (ABCDE -> ABDE)
④ 아래 :(2, 3)→(3, 3)로, 변경 후 문자열에서 세 번째 문자가 대상이 됩니다(2 -> 3이므로). 변경 후 문자열 ABDCE에서 세 번째 해당 문자(D)를 변경 전 문자의 세 번째 위치에 삽입합니다. (ABDE -> ABDDE)
⑤ 오른쪽 :(3, 3)→(3, 4)로, 변경 전 문자열의 네 번째 문자가 대상이 됩니다.(3 -> 4이므로). 변경 전 문자열 ABDDE에서 네 번째 문자열의 문자(D)를 현재 위치에서 삭제합니다. (ABDDE -> ABDE)
⑥ 아래 :(3, 4)→(4, 4)로, 변경 후 문자열에서 네 번째 문자가 대상이 됩니다.(3 -> 4이므로). 변경 후 문자열 ABDCE에서 네 번째 문자(C)를 변경 전 문자의 네 번째 위치에 삽입합니다. (ABDE -> ABDCE)
⑦ 대각선 : 그대로 진행합니다. (ABCDE -> ABCDE)
이렇게 진행하게 된 결과를 시각화하면 다음과 같습니다. Git을 사용할 때 많이 보던 모습입니다.

지금까지 두 아이템을 효율적으로 비교하는 방식을 알아보았습니다. 효율적으로 변경 사항을 반영할 수 있어, 실제로 많은 프로그램에서도 이 알고리즘이 응용되고 있습니다.
원활한 이해를 위해 Blocking - Non-Blocking 지식이 필요합니다.
짧지 않은 분량만큼 알고리즘을 설명했던 데는 이유가 있습니다.
앞서 언급하였듯, RecyclerView의 Adapter에서 개발자 대신 변경 사항을 효율적으로 반영해주는 역할을 해주는 DiffUtil이 해당 알고리즘을 사용하기 때문입니다.
DiffUtil의 공식 문서 설명을 보도록 하겠습니다.
DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one.
It can be used to calculate updates for a RecyclerView Adapter. (...계속...)
DiffUtil uses Eugene W. Myers's difference algorithm to calculate the minimal number of updates to convert one list into another. (...계속...)
방금 살펴본 알고리즘이 두 리스트, 즉 변경 전 리스트와 변경 후 리스트를 비교하는 수단으로 사용되고 있음을 알 수 있습니다. DiffUtil에서는 이 비교 작업을 통해 최적의 방법으로 변경 사항을 반영하고 있던 것이었습니다.
실제로 DiffUtil 내부에는 calculateDiff라는 메소드가 존재하는데, 여기서 Diff 알고리즘이 사용되고 있음을 확인할 수 있습니다.
public static DiffResult calculateDiff(
@NonNull Callback cb,
) { /* ... 계산 후 결과 반환 ... */ }
코드에서 주목해볼 만한 점은, DiffResult를 반환하고 있다는 것입니다.
You can consume the updates ( ... 선택지 1 생략 ... )
or directly stream the results into a RecyclerView.Adapter viadispatchUpdatesTo(RecyclerView.Adapter).
해당 객체는 DiffUtil 내부에 정의되어 있는 객체로, 말 그대로 DiffUtil에서 계산된 두 데이터 세트 간 변경 사항을 담고 있습니다. 이 변경 사항을 dispatchUpdatesTo라는 메소드를 통해 RecyclerView의 Adapter로 전달할 수 있습니다.
public void dispatchUpdatesTo(@NonNull final RecyclerView.Adapter adapter) {
dispatchUpdatesTo(new AdapterListUpdateCallback(adapter));
}
그런데 또 하나의 요소가 눈에 띕니다. RecyclerView.Adapter가 AdapterListUpdateCallback에 싸여있습니다. 객체명을 통하여 DiffResult를 통해 변경 내용을 파악하고, 데이터 세트의 일련의 변경 사항(Update)이 일어났을 때 RecyclerView.Adapter의 특정 기능을 활용하는 콜백들을 모아놓은 객체로 추측해볼 수 있습니다.
그리고 AdapterListUpdateCallback 클래스를 타고 들어가 보면, 익숙한 notify 관련 메서드를 마주할 수 있습니다.
public final class AdapterListUpdateCallback implements ListUpdateCallback {
@NonNull
private final RecyclerView.Adapter mAdapter;
public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) {
mAdapter = adapter;
}
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
mAdapter.notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
mAdapter.notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
mAdapter.notifyItemRangeChanged(position, count, payload);
}
}
감지된 변경사항에 따라 적절한 notify 메서드를 호출해주고 있습니다!
이러한 방식으로 DiffUtil은 개발자들의 데이터 세트 갱신을 손쉽게 진행할 수 있도록 도와주고 있던 것이었습니다.
DiffUtil이 좋은 것을 알았으니, 이제 이것을 어떻게 사용해야 할지 알아보겠습니다.
그 전에, DiffUtil의 calculateDiff()의 특성을 알아볼 필요가 있습니다.
기본적으로 calculateDiff()는 메인 스레드에서 동작하도록 설계되어 있습니다. 그리고 보통 규모가 큰 데이터를 처리할 수록 더 많은 시간이 걸리는 것이 사실입니다. 따라서, 규모가 큰 데이터 세트에 발생한 변경사항이 어떤 것인지 계산할 때는, 메인 스레드를 긴 시간 동안 Block시킬 수 있다는 위험이 있습니다.
하지만 메인 스레드에서는, Blocking한 작업을 수행하면 안 됩니다. (여기서 이유는 설명하지 않겠습니다.) 사용자 경험을 위하여 백그라운드 스레드에서 적절히 연산을 처리한 후, 변경 사항이 확정되었을 때 비로소 메인 스레드에 반영하는 것이 바람직하다고 볼 수 있습니다.
개발자들이 이 과정들을 큰 수고 없이 할 수 있도록 하는 도구가 바로 AsyncListDiffer입니다.
Helper for computing the difference between two lists via DiffUtil on a background thread.
It can be connected to a RecyclerView.Adapter, and will signal the adapter of changes between sumbitted lists.
보통 이것을 RecyclerView.Adapter에서 사용하는데, AsyncListDiffer에서 제공하는 기능을 통해 기존 RecyclerView.Adapter에서 직접 보유하고 있던 현재 데이터 세트의 관리와 변경 사항 반영 작업을 위임할 수 있습니다.
public class AsyncListDiffer<T> {
public AsyncListDiffer(@NonNull RecyclerView.Adapter adapter,
@NonNull DiffUtil.ItemCallback<T> diffCallback) { /* ... */ }
// ...
@NonNull
public List<T> getCurrentList() {
return mReadOnlyList;
}
public void submitList(@Nullable final List<T> newList) {
submitList(newList, null);
}
}
getCurrentList() : 현재 데이터 세트를 가져올 수 있습니다.submitList(List<T>) : 변경된 데이터 세트를 전달받아, DiffUtil이 백그라운드 스레드에서 변경 사항을 계산한 후 메인 스레드를 통해 UI에 반영합니다.한 가지 주목할 만한 점이 더 있습니다. 코드에서 AsyncListDiffer의 생성자 함수를 보면, 매개변수로 RecyclerView.Adapter 뿐만 아니라 DiffUtil.ItemCallback<T>라는 것도 요구하고 있습니다.
결론만 살펴보면, 해당 콜백 객체는 submitList()에서 백그라운드 스레드를 동작시키고 calculateDiff()를 호출할 때 매개변수로 사용됩니다.
public void submitList(@Nullable final List<T> newList,
@Nullable final Runnable commitCallback) {
// ...
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result =
DiffUtil.calculateDiff(new DiffUtil.Callback() { /* ... */ })
// UI에 반영
}
}
}
// DiffUtil
public static DiffResult calculateDiff(
@NonNull Callback cb,
) { /* ... 계산 후 결과 반환 ... */ }
이 콜백이 사용되는 목적은, DiffUtil에서 변경 사항 계산 시 변경 전/후 두 리스트 아이템의 차이 여부를 효율적으로 파악할 수 있도록 하기 위함입니다.
Callback for calculating the diff between two non-null items in a list.
개발자가 DiffUtils.ItemCallback를 구현할 때는, 두 개의 메서드를 오버라이드해야 합니다.
areItemsTheSame : 아이템이 같은 것인지(주로 id 등 식별자를 활용)areContentsTheSame : 동일한 내용을 가지고 있는 것인지(동등성)두 가지 메소드를 활용해, DiffUtil에서 아이템 변경 여부를 파악하는 데 활용합니다.
areItemsTheSame을 먼저 계산했을 때, false라면 업데이트가 필요areContentsTheSame를 호출, 그 결과가 false라면 업데이트 필요public abstract static class ItemCallback<T> {
public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem);
public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem);
}
지금까지 RecyclerView에 변경사항을 효율적으로 반영하기 위한 매커니즘을 알아보았습니다. 이제 본격적으로 DiffUtil, AsyncListDiffer를 활용하여 손쉽게 변경사항을 반영해보도록 하겠습니다.
BenefitDiffUtilCallback.kt
AsyncListDiffer를 사용하기 위해 필요한 두 개의 매개변수 중 하나인, DiffUtil.ItemCallback<T>을 상속받은 클래스를 구현해보겠습니다.
<T> 자리에 들어가는 제네릭은 비교 대상이 되는 객체입니다. 현재 BenefitRecyclerViewAdapter에서는 BenefitListItem를 취급하고 있기 때문에, 제네릭 자리에 해당 타입을 사용합니다.
이후 필요한 두 개의 메서드를 구현합니다.
areItemsTheSame : id 식별자를 활용areContentsTheSame : 동일한 내용을 가지고 있는 것인지 판단합니다. BenefitListItem이 data class로 구현되어 있으므로, 동등성을 판별하여 내용이 같은지의 여부를 결정합니다.class BenefitDiffUtilCallback : DiffUtil.ItemCallback<BenefitListItem>() {
override fun areItemsTheSame(oldItem: BenefitListItem, newItem: BenefitListItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: BenefitListItem, newItem: BenefitListItem): Boolean {
return oldItem == newItem
}
}
BenefitRecyclerViewAdapter.kt
어댑터 내부에 AsyncListDiffer 객체를 둡니다. 이제 해당 객체에서 데이터 세트를 보유하고 변경사항을 처리하게 됩니다.
AsyncListDiffer에서 데이터 세트를 관리하도록, 기존에 생성자 파라미터이자 프로퍼티로 관리하던 benefits 리스트는 제거합니다. 또한 변경사항을 반영하는 notify 관련 메서드들의 사용들도 모두 제거합니다.
이제 AsyncListDiffer의 submitList를 활용하여 업데이트를 자동으로 반영하고, currentList를 통해 현재 데이터 세트를 불러올 수 있게 됩니다.
class BenefitRecyclerViewAdapter
: RecyclerView.Adapter<BenefitScreenRecyclerViewHolder>() {
private val asyncListDiffer = AsyncListDiffer(this, BenefitDiffUtilCallback())
// onCreateViewHolder
override fun onBindViewHolder(holder: BenefitScreenRecyclerViewHolder, position: Int) {
val benefits = asyncListDiffer.currentList
when (holder) {
is BenefitScreenRecyclerViewHolder.BenefitViewHolder -> {
holder.bind(benefits[position].viewItem as BenefitListViewItem.Benefit)
}
is BenefitScreenRecyclerViewHolder.AdvertisementViewHolder -> {
holder.bind(benefits[position].viewItem as BenefitListViewItem.Advertisement)
}
}
}
override fun getItemCount(): Int = asyncListDiffer.currentList.count()
override fun getItemViewType(position: Int): Int {
return when (asyncListDiffer.currentList[position].viewItem) {
is BenefitListViewItem.Benefit -> BenefitListViewItem.VIEW_TYPE_BENEFIT
is BenefitListViewItem.Advertisement -> BenefitListViewItem.VIEW_TYPE_ADVERTISEMENT
}
}
// notify 관련 메서드 모두 제거
fun submitBenefitData(benefits: List<BenefitListItem>) {
asyncListDiffer.submitList(benefits)
}
}
BenefitsActivity.kt
어댑터를 생성하는 과정에서 사라진 매개변수를 제거하고, 데이터 세트를 표출하기 위해 어댑터에 초기 데이터 리스트를 전달합니다.
class BenefitsActivity : AppCompatActivity() {
// ...
private val adapter = BenefitListAdapter().apply {
submitBenefitData(benefitRepository.benefits)
}
// ...
}
[결과 9]와 동일하게, 효율적으로 데이터 세트가 업데이트되고 애니메이션 효과 역시 그대로 표출되고 있습니다.
This class is a convenience wrapper around AsyncListDiffer
[코드 10]의 과정 마저 누군가에게는 번거로울 수 있습니다.
AsyncListDiffer를 손쉽게 사용할 수 있도록 래핑한 RecyclerView.Adapter가 존재하는데, 이것이 바로 ListAdapter입니다.
이전 게시물에서, RecyclerView의 Adapter와 BaseAdapter 간 관계를 살펴보았습니다.

여기서 설명하는 ListAdapter는 androidx.recyclerview.widget의 어댑터로, RecyclerView.Adapter를 상속받습니다. 즉 RecyclerView.Adapter의 동작에 더해 비동기적으로 데이터 세트의 변경 사항을 손쉽게 반영할 수 있도록 도와주는 객체인 것입니다.
ListAdapter<T, VH>(diffCallback: DiffUtil.ItemCallback<T>)의 사용 방법은 매우 간단합니다.
첫 번째 T에는 AsyncListDiffer에서 아이템 비교를 위해 사용한 제네릭인 T를 그대로 사용하면 되고, 두 번째 VH에는 어댑터에서 사용하는 ViewHolder의 타입을 명시하면 됩니다.
그리고 생성자 매개변수에 만들어둔 DiffUtil.ItemCallback<T>를 사용하면 사용을 위한 모든 준비가 끝납니다.
BenefitListAdapter.kt
기존 BenefitRecyclerViewAdapter를 변형하여 BenefitListAdapter를 만듭니다.
이 상태에서 두 가지 작업만 진행해주면 되는데, BenefitRecyclerViewAdapter에서 만들어놓았던 asyncListDiffer 프로퍼티를 제거하고, 클래스가 ListAdapter<T, VH>를 상속받도록 하면 끝입니다.
class BenefitListAdapter
: ListAdapter<BenefitListItem, BenefitScreenRecyclerViewHolder>(BenefitDiffUtilCallback()) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BenefitScreenRecyclerViewHolder {
val inflater = LayoutInflater.from(parent.context)
val viewHolder = when (viewType) {
BenefitListViewItem.VIEW_TYPE_BENEFIT -> {
val view = inflater.inflate(R.layout.item_benefit, parent, false)
BenefitScreenRecyclerViewHolder.BenefitViewHolder(view)
}
BenefitListViewItem.VIEW_TYPE_ADVERTISEMENT -> {
val view = inflater.inflate(R.layout.item_advertisement, parent, false)
BenefitScreenRecyclerViewHolder.AdvertisementViewHolder(view)
}
else -> throw RuntimeException("Unknown view type")
}
return viewHolder
}
override fun onBindViewHolder(holder: BenefitScreenRecyclerViewHolder, position: Int) {
when (holder) {
is BenefitScreenRecyclerViewHolder.BenefitViewHolder -> {
holder.bind(currentList[position].viewItem as BenefitListViewItem.Benefit)
}
is BenefitScreenRecyclerViewHolder.AdvertisementViewHolder -> {
holder.bind(currentList[position].viewItem as BenefitListViewItem.Advertisement)
}
}
}
override fun getItemCount(): Int = currentList.count()
override fun getItemViewType(position: Int): Int {
return when (currentList[position].viewItem) {
is BenefitListViewItem.Benefit -> BenefitListViewItem.VIEW_TYPE_BENEFIT
is BenefitListViewItem.Advertisement -> BenefitListViewItem.VIEW_TYPE_ADVERTISEMENT
}
}
fun submitBenefitData(benefits: List<BenefitListItem>) {
submitList(benefits)
}
}
처음 만들었던 어댑터에 비하면, 코드가 전체적으로 깔끔해졌습니다!
[결과 9], [결과 10]과 동일합니다.
이번 편에서는 RecyclerView를 사용하며 더욱 쉽게 데이터 세트의 변동사항을 효율적으로 반영하는 방법을 알아보았습니다.
특히나 이번에 다룬 내용은 꽤 어려운 내용이었지만, RecyclerView는 안드로이드 개발을 한다면 무조건 사용하게 되는 라이브러리인 만큼 한 번쯤은 짚고 넘어가야 할 내용이 아니었나 생각합니다.
사실 RecyclerView와 관련하여 더 다루고 싶은 내용은 많지만, 다음에 시간이 나면 다뤄보도록 하겠습니다.
지금까지 글을 읽어오셨다면, 오늘날 우리가 사용하고 있는 RecyclerView가 만들어지기까지 안드로이드 측에서 많은 시행착오와 보완 과정이 수반되었다는 것을 느끼셨을 것입니다. 이 글이 안드로이드 개발의 역사 속에서 데이터 세트를 표출하는 방식의 발전상을 몸소 느껴보는, 흥미로운 경험이 되셨길 바라겠습니다!
https://elsboo.tistory.com/39
https://medium.com/skyrise/the-myers-diff-algorithm-and-kotlin-observable-properties-69dfb18541b
https://developer.android.com/reference/androidx/recyclerview/widget/AsyncListDiffer
https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil
https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil.ItemCallback