이동식 저장소

LiveData, Room 그리고 ListAdapter 본문

프로젝트/MyVoca

LiveData, Room 그리고 ListAdapter

해스끼 2021. 4. 8. 22:55

ListAdapter

Android에서 여러 개의 항목을 보여줘야 할 때는 거의 ``RecyclerView``를 사용한다. ``RecyclerView``에 데이터를 제공하는 방법은 여러 가지가 있는데, 최근에 ``ListAdapter``를 알게 되어 MyVoca에 적용해 보았다.

 

참고: DiffUtil and data binding with RecyclerView - Codelabs for Android Kotlin Fundamentals (Google)

 

기존 ``RecyclerView.Adapter``는 리스트가 변경될 때마다 ``notify...`` 메소드를 실행해줘야 값이 제대로 보인다. 그런데 데이터의 삽입, 삭제, 수정 등 상황마다 실행해야 하는 메소드가 달라서 (솔직히) 귀찮다. 그래서 궁극의 ``notifyDataSetChanged()``로 퉁치는 경우도 있지만, 이렇게 하면 전체 데이터를 전부 다시 그리기 때문에 성능상 좋지 않다.

 

``ListAdapter``는 리스트의 변경사항을 자동으로 추적하여 내부적으로 ``notify...`` 메소드를 실행한다. 우리는 두 데이터 객체의 동일성과 동등성을 판정하는 클래스만 작성하면 된다. 자세한 내용은 위의 링크를 참고하자.

그런데

난 왜 갱신이 안 되지? 뭔가 버그가 있다. 말로 설명하기 어려우니 직접 한번 보자.

 

 

``RecyclerView``에서 아이템을 왼쪽으로 스와이프하면 아이템이 삭제되도록 하는 기능을 구현하였다. 그런데``RecyclerView``가 제대로 갱신되지 않는다. 단어가 제대로 삭제된 것 같지도 않다. 단어를 하나 지웠음에도 단어 수가 그대로 4개이다. 심지어 취소 버튼을 누르면 앱이 죽네? 

 

절대로 사소한 문제가 아니다! 데이터 누수가 생길 수도 있기 때문이다.

원인: 리팩토링 실수

beta-1.12.0에서 코루틴을 도입하면서 코드를 대대적으로 리팩토링하였다. 그런데 ``ViewModel`` 코드를 리팩토링하는 과정에서 실수가 있었다.

// ViewModel

// RecyclerView에 보여야 하는 단어
private val _currentVocabulary = MutableLiveData<MutableList<RoomVocabulary?>?>()
val currentVocabulary: LiveData<MutableList<RoomVocabulary?>?>
    get() = _currentVocabulary

...

// 단어 삭제 메소드
fun deleteItem(position: Int) = viewModelScope.launch(Dispatchers.IO) {
    val target = currentVocabulary.value?.get(position) ?: return@launch
    _currentVocabulary.value?.removeAt(position)              // 1
    vocaRepository.deleteVocabulary(target.toVocabulary())    // 2
}

1번 줄은 리스트에서 단어를 지우고, 2번 줄은 데이터베이스에서 단어를 지운다. 그런데 1번과 2번 모두 ``LiveData``의 값을 바꾸었으므로 각각 observer를 호출하게 된다. 

// observer
viewModel.currentVocabulary.observe(viewLifecycleOwner) { it ->
        it?.let { vocaRecyclerViewAdapter?.submitList(it) }  // 3. 리스트 갱신
        vocaRecyclerViewAdapter?.notifyDataSetChanged()      // 4
}

4번 줄에서 ``notify`` 메소드를 실행하고 있다. 3번에서 알아서 실행될 텐데! 이러면 데이터 누수를 막을 수 있지만, 그래픽이 매우 뚝뚝 끊기는 문제가 발생한다. 사실 이건 beta-1.12.0에서 코루틴을 도입할 때부터 있었던 이슈라서 꼭 고치고 싶기도 했다.

해결: DB에 맡겨라

코루틴을 도입하여 얻은 성과 중 하나는 내부 DB의 변경사항이 ``currentVocabulary``에 그대로 반영된다는 점이다. 그러니까 단어를 ``vocaRepository``에서만 지우면 ``currentVocabulary``에도 그대로 반영된다는 뜻이다.

 

즉 1번 줄을 없애야 한다. 4번 줄은 1번 줄이 없었더라면 애초에 필요없는 코드이므로 지워도 된다. 아니 지워야 한다. 그래야 UI가 더 부드럽게 작동한다.

 

삭제, 취소, 단어 개수 모두 정상 작동

 

해결!


해묵은 버그를 수정하여 기쁜 마음에 글을 작성해 보았다.

'프로젝트 > MyVoca' 카테고리의 다른 글

MyVoca 1.13.0 출시  (0) 2021.06.13
MyVoca 1.12.4 출시  (0) 2021.05.26
MyVoca 1.12.3 출시  (0) 2021.05.10
Bitrise로 Continuous Delivery 제공하기  (0) 2021.04.20
Google Play에 MyVoca 출시  (0) 2021.03.18
Comments