RecyclerView로 전달하는 상태 객체에 lambda로 이벤트 처리 시 주의사항
안드로이드 공식 문서에 자세히 언급되지 않은 내용을 살펴봅니다.
안드로이드 앱을 개발해보면, ViewModel에서 LiveData나 Flow로 UI 상태를 노출하고 상태가 변경될 때마다 Activity나 Fragment에서 UI를 업데이트하는 코드를 작성하는 경우가 많습니다. 반대로 UI에서 Click과 같은 이벤트가 발생하면, ViewModel의 함수를 호출합니다.
View 시스템에서 제공하는 목록형 UI인 RecyclerView도 동일한데요. 목록 UI를 업데이트할 때는 Adapter를 통해 요청하고, Adapter 내의 각 항목에서 이벤트가 발생하면 ViewHolder - Adapter - Activity/Fragment - ViewModel 순으로 전달하는 것이 일반적인 방법입니다.
여기서 Activity/Fragment는 이어주는 역할만 할 뿐이어서, 이 부분을 개선하려는 여러가지 노력들이 있었던 것 같은데요. 작년에 안드로이드 개발자 공식 문서에 앱 아키텍처 문서가 추가되면서, 이와 관련된 RecyclerView의 사용자 이벤트 부분이 짧게 추가되어 이 내용을 조금 더 다뤄보려 합니다.
문서의 내용은 간단합니다. UI 상태 클래스에 onBookmark
처럼 이벤트 처리를 위한 구현을 포함하는 방법입니다. UI 상태만 전달하고, 이벤트 처리할 때는 Activity/Fragment가 중간에 끼어들지 않는 부분이 핵심입니다.
잠재된 문제점
예전부터 이렇게 사용하셨던 분들이라면 이미 어떤 문제인지 알고 계실 것 같은데요. 문서를 통해 처음 접한 분들은 코드 일부분만 있어서 “이렇게 사용할 수도 있구나” 하고 넘어갈 수도 있을 것 같지만, 실제로 적용해 보면 문제가 발생할 수 있어서 주의해야 합니다.
위의 예제를 기준으로 설명해보겠습니다. Bookmark 버튼을 누르면 repository에 갱신을 요청하고, API 혹은 DB 등의 데이터가 변경되면 latestNews
가 변경되어 newsListUiItems
으로 새로운 목록이 방출되는 흐름입니다. 따라서 RecyclerView가 여러번 업데이트될 수 있겠죠.
RecyclerView는 UI 업데이트를 최소화할 수 있게 ListAdapter를 제공하고 있습니다. 보통은 DiffUtil.ItemCallback
에서 areContentsTheSame
함수를 구현하여 UI 상태가 변경되었는지를 알려주면 됩니다. 보통은 UI 상태를 data class로 정의하여, 자동 생성된 equals 함수를 이용할 것 같습니다.
그런데 NewsItemUiState
를 생성할 때 onBookmark
를 lambda로 정의하면, 상태의 값들이 동일하더라도 클래스가 생성될 때마다 lambda에 의해서 내용이 변경된 것으로 인식됩니다. 따라서 newsListUiItems
가 새로운 목록을 방출할 때마다 불필요하게 모든 목록을 다시 그리게 됩니다! 🤦
해결 방법
lambda로 인한 불필요한 업데이트를 개선해볼까요?
첫 번째 방법은 NewsItemUiState를 생성할 때, onBookmark
를 lambda로 정의하는 것 대신 메소드 참조를 이용하는 방법입니다. 이러면 onBookmark
가 변경되지 않으므로 areContentsTheSame
함수가 의도한대로 동작하겠죠?
다만 이렇게 수정하면, onBookmark
를 호출하는 코드도 수정되어야 하는 부분이 조금 아쉽습니다. 더 좋은 방법은 없을까요?
lambda를 생성자 매개변수에서 클래스 본문으로 옮기는 방법이 있습니다. 이러면 data 키워드에서 자동 생성되는 equals() 함수의 비교대상에서 빠지고, areContentsTheSame
함수가 의도한대로 동작합니다. 👏
오늘은 안드로이드 공식 문서에 추가된 RecyclerView의 사용자 이벤트 문서의 내용을 구현할 때 주의해야 할 점을 간단히 살펴봤습니다. 위에서 소개한 해결 방법 이외에도 다양한 방법이 있으니, 문제점 만을 인식하고 직접 개선방법을 고민해 보는 것도 좋을 것 같습니다. 😄