[Android / Kotlin] ListAdapter와 DiffUtil

Subeen·2024년 1월 25일
0

Android

목록 보기
51/71

RecyclerView.Adapter를 사용하면 데이터가 추가, 삭제, 변경이 되었을 때 notifyDataSetChanged()를 통해 리스트 전체를 업데이트 한다. notifyDataSetChanged()를 사용할 경우 데이터셋의 변경을 감지하면 리스트 전체를 변경하기에 시간 지연이 발생할 가능성이 높다.
이에 반해 DiffUtil현재 데이터셋과 변경될 데이터셋을 비교하여 변경되어야 할 데이터만 변경해주기에 빠른 시간 내에 효율적으로 데이터를 변경할 수 있다.

ListAdapter 구현하기

DataClass

@Parcelize
data class TodoModel(
    val id: String = UUID.randomUUID().toString(),
    val title: String,
    val content: String,
    val isBookmarked: Boolean
) : Parcelable

DiffUtil

  • DiffUtil은 oldItem과 newItem 두 데이터셋을 비교하여 값이 변경된 부분만을 RecyclerView에 알려줄 수 있다.
  • areItemsTheSame
    • 기존 어댑터와 새롭게 변경되는 어댑터의 아이템이 같은지 확인하며 각 아이템의 고유 id 값을 활용하여 비교한다.
  • areContentsTheSame
    • 기존 어댑터와 변경되는 어댑터의 아이템 안의 내용을 비교한다.
    • areItemsTheSame에서 true가 반환 될 경우 추가적으로 비교하기 위해서 사용된다.
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 형태 
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)
            }
        }
    }
}

Fragment

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 사용기

profile
개발 공부 기록 🌱

0개의 댓글