RecyclerView.Adapter를 사용하면 데이터가 추가, 삭제, 변경이 되었을 때
notifyDataSetChanged()
를 통해 리스트 전체를 업데이트 한다.notifyDataSetChanged()
를 사용할 경우 데이터셋의 변경을 감지하면 리스트 전체를 변경하기에 시간 지연이 발생할 가능성이 높다.
이에 반해DiffUtil
은 현재 데이터셋과 변경될 데이터셋을 비교하여 변경되어야 할 데이터만 변경해주기에 빠른 시간 내에 효율적으로 데이터를 변경할 수 있다.
@Parcelize
data class TodoModel(
val id: String = UUID.randomUUID().toString(),
val title: String,
val content: String,
val isBookmarked: Boolean
) : Parcelable
areItemsTheSame
areContentsTheSame
object TodoDiffUtil : DiffUtil.ItemCallback<TodoModel>() {
override fun areItemsTheSame(oldItem: TodoModel, newItem: TodoModel): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: TodoModel, newItem: TodoModel): Boolean {
return oldItem == newItem
}
}
// ListAdapter 형태
ListAdapter<데이터 클래스, 리사이클러뷰 뷰홀더>(콜백)
getItem(position: Int)
: 클래스 내부에서 구현할 때 사용하며 어댑터 내 List Indexing을 할 때 활용할 수 있다.getCurrentList()
: 어댑터가 가지고 있는 리스트를 가져올 때 사용한다.submitList(MutableList <T> list)
: 리스트 항목을 변경하고 싶을 때 사용한다.class TodoListAdapter : ListAdapter<TodoModel, TodoListAdapter.ViewHolder>(TodoDiffUtil) {
private var listener: OnItemClickListener? = null
fun setItemChangedListener(listener: OnItemClickListener) {
this.listener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding =
ItemTodoListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item)
}
inner class ViewHolder(binding: ItemTodoListBinding) : RecyclerView.ViewHolder(binding.root) {
private val tvTodoTitle = binding.tvTodoTitle
private val tvTodoContent = binding.tvTodoContent
private val switchTodo = binding.switchTodo
private val layout = binding.itemLayout
fun bind(item: TodoModel) {
tvTodoTitle.text = item.title
tvTodoContent.text = item.content
switchTodo.isChecked = item.isBookmarked
switchTodo.setOnClickListener {
listener?.onClickSwitch(item)
}
layout.setOnClickListener {
listener?.onClickItem(item)
}
}
}
}
class TodoFragment : Fragment() {
private var _binding: FragmentTodoBinding? = null
private val binding: FragmentTodoBinding get() = _binding!!
private val viewModel: TodoViewModel by activityViewModels()
// ListAdapter 생성
private val todoListAdapter by lazy {
TodoListAdapter()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentTodoBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}
private fun initView() {
// ListAdapter 설정
binding.recyclerViewTodo.adapter = todoListAdapter
// Listener 설정
todoListAdapter.setItemChangedListener(object : OnItemClickListener {
override fun onClickSwitch(todoModel: TodoModel) {
viewModel.updateBookmarkStatus(todoModel)
}
override fun onClickItem(todoModel: TodoModel) {
updateTodoLauncher.launch(
CreateTodoActivity.newIntent(
context = requireContext(),
contentType = TodoContentType.UPDATE,
todoModel = todoModel
)
)
}
})
}
private fun initViewModel() = with(viewModel) {
uiState.observe(viewLifecycleOwner) { state ->
// submitList()를 사용하여 리스트 항목 변경
todoListAdapter.submitList(state.list.filter { !it.isBookmarked })
}
}
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}
참조
안드로이드 [Kotlin] - RecyclerView에서 ListAdapter와 DiffUtil 사용기