지금까지 내가 안드로이드에서 리스트뷰를 구현했던 학습 과정은 아래의 순서였다.
- 기본 ListView
- Custom ListView
- RecyclerView
- DataBinding한 RecyclerView
이제 다음 단계는 ListAdapter를 이용한 RecyclerView 구현이다.
위의 4번을 베이스로 ListAdapter를 활용하는 것을 적어보겠다.
위의 2번과 3번의 차이가 Item 내에 view에 데이터를 bind할 때 ViewHoloder패턴의 강제 적용 유무였다면,
ListAdapter를 사용한 RecyclerView는 Item 업데이트가 있을 때 이를 변경된 부분만 자동으로 탐지해서 notify 해준다.
ListAdapter에서는 List를 저장하는 자료구조를 따로 만들지 않아도 된다.
어댑터 자체에서 Data List를 정의하기 때문이다.
ListAdapter에서 사용되는 DiffUtil은 Eugene W. Myers의 difference 알고리즘을 사용한다.
이를 이용하여 기존에 있는 데이터와 새로 들어온 데이터로부터 업데이트를 구분한다.
서로 같은 데이터인지 구분하는 로직은 우리가 정의해야 한다.
아래는 데이터를 구분하는 로직이다.
object BoardDiffCallback : DiffUtil.ItemCallback<BoardInfo>(){
override fun areItemsTheSame(oldItem: BoardInfo, newItem: BoardInfo): Boolean {
return oldItem.postId == newItem.postId
}
override fun areContentsTheSame(oldItem: BoardInfo, newItem: BoardInfo): Boolean {
return oldItem == newItem
}
}
- areItemsTheSame()은 어댑터의 기존 데이터와 새로 온 데이터가 동일한 아이탬인지 확인하는 기준을 정의한다.
True를 반환하면 아래의 areContentsTheSame()을 call한다. - areContentsTheSame()은 아이탬의 내용이 서로 일치하는지 확인한다.
여기까지 true라면 ListAdapter는 해당 아이탬을 update시킬 필요가 없을 것이다.
아래는 adapter의 전체 코드다.
위에서 정의한 BoardDiffCallback을 이용해서 BoardListAdapter를 만들었다.
기존 RecyclerView와 또 하나 다른점은 onBindViewHolder()에서 holder.bind(getItem(position))으로 바인딩한다는 것이다.
위에서 언급했듯, 어댑터 자체에 Data List를 정의하기 때문에 바로 getItem()으로 접근할 수 있다.
BoardAdapter.kt
class BoardListAdapter: ListAdapter<BoardInfo, BoardListAdapter.BoardListViewHolder>(
BoardDiffCallback
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BoardListViewHolder {
val binding:ItemBoardListBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.item_board_list,parent,false)
return BoardListViewHolder(binding)
}
override fun onBindViewHolder(holder: BoardListViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class BoardListViewHolder(private val binding: ItemBoardListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(boardInfo: BoardInfo) {
binding.boardItem = boardInfo
binding.executePendingBindings()
}
}
object BoardDiffCallback : DiffUtil.ItemCallback<BoardInfo>(){
override fun areItemsTheSame(oldItem: BoardInfo, newItem: BoardInfo): Boolean {
return oldItem.postId == newItem.postId
}
override fun areContentsTheSame(oldItem: BoardInfo, newItem: BoardInfo): Boolean {
return oldItem == newItem
}
}
}
이제 Adapter를 Activity/Fragment에서 사용하려면 아래와 같이 쓰면 된다.
ViewModel.kt
private val _boardList = ArrayList<BoardInfo>()
val boardList: MutableLiveData<ArrayList<BoardInfo>> by lazy{
MutableLiveData<ArrayList<BoardInfo>>()
}
Activity/Fragment.kt
boardAdapter = BoardListAdapter()
boardRecyclerView.adapter = boardAdapter
boardViewModel.boardList.observe(binding.lifecycleOwner!!, {
boardAdapter.submitList(it.toMutableList())
})
submitList()의 매개변수에 it.toMutableList()를 넣었는데
it 자체만 넣으면 이상하게 submit()이 처음에만 잘 되고 회면회전이나 다른 activity 갔다왔을 때 사라져있다.
이에 대해 스택오버플로에서 솔루션을 주신 것 중 하나를 따라 한 것인데
근본적인 해결방안이 맞는지는 앞으로 계속 써보면서 고민해볼 것이다.
stackoverflow.com/questions/49726385/listadapter-not-updating-item-in-recyclerview
추가)
뒤늦게 .toMutableList()를 넣기 전에 문제가 생겼던 이유를 알게 되었다. 아래는 관련 포스팅이다. 결론부터 이야기하면, 같은 레퍼런스를 갖는 두 객체에 대해 전달을 하지 않기 때문이다. submitList() 를 할 때 새로운 객체를 만들던지 참조값을 다르게 해야한다.
https://yk-coding-letter.tistory.com/27
'Programming > Android' 카테고리의 다른 글
[Android] Koin + Unit Test 하기 (0) | 2021.05.21 |
---|---|
[Android] ViewPager2 + BottomNavigation 으로 Fragment 관리하기 (0) | 2021.05.16 |
MVVM에서 ViewModel의 Event 전달하기2(Event Wrapper) (0) | 2021.04.28 |
MVVM에서 ViewModel의 Event 전달하기(Event Wrapper) (0) | 2021.04.27 |
디자이너 없이 아이콘 이미지 받을 때 유용한 사이트 (0) | 2021.04.26 |