1. 사건 배경
2. 증상
3. 리스트를 업데이트하는 5가지 방법
3-1. 전체 업데이트
3-1-1. notifyDataSetChanged
3-2. 변경
3-2-1. notifyItemChanged
3-2-2. notifyItemRangeChanged
3-3. 추가
3-3-1. notifyItemInserted
3-3-2. notifyItemRangeInserted
3-4. 삭제
3-4-1. notifyItemRemoved
3-4-2. notifyItemRangeRemoved
3-5. 이동
3-5-1. notifyItemMoved
4. 해결 방법
5. 또 다른 문제 및 해결 방법
1. 사건 배경
블로그에 포스팅하기 위해서 양방향 바인딩 + recyclerView를 공부하고 있었다.
배달의민족 공지사항을 불러와 리스트로 뿌려주는 코드를 짰는데...
리스트를 잘 불러오긴 불러오는데... 다음과 같은 문제가 있었다.
2. 증상
문제가 되는 부분이 너무 순식간에 지나가서 슬로우 모션 촬영에다가 0.2배속을 해야 눈으로 확인할 수 있었다.
움짤을 보면 알겠지만 '다음 페이지'를 누르면 리스트가 2번 바뀐다.
천천히 정지해가면서 보니까 위와 같은 과정으로 페이지가 바뀌고 있었다.
1페이지에 1번부터 10번까지 공지사항이 보이고 있는데
'다음 페이지' 버튼을 누르면 잠깐 한 0.1초 정도? 6번부터 10번까지 공지사항이 위로 올라오고
그 다음에서야 2페이지가 표시되었다.
여태까지 recyclerView를 잘 써왔는데 대체 이게 무슨 증상인지;;;; 😓
원인이 어댑터에게 값의 변화를 알리는 메서드에 있다고 생각되어 이 부분에 대해 다음과 같이 조사를 했다.
3. 리스트를 업데이트하는 5가지 방법
3-1. 전체 업데이트
3-1-1. notifyDataSetChanged
사용하기 편해서 나 같은 초보자들이 가장 많이 사용하는 메서드이다.
많은 인터넷 글에서 "recyclerView의 리스트를 업데이트할 때 사용하는 메서드" 정도로 소개하고 있다.
하지만 좀 더 자세히 알고 상황에 맞게 사용해야 하지 않을까 싶다. (이 글을 작성하는 이유이기도 하다)
notifyDataSetChanged는 리스트의 크기와 아이템이 둘 다 변경되는 경우에 사용하면 된다.
어댑터에게 "야! 이제 리스트 크기도 변할 거고, 아이템도 새로운 게 들어올 거야. 다시 새로 그려!"라고 알려주는 것이다.
하지만 리스트의 크기는 동일한데 아이템만 바뀌는 경우라든지
아이템의 순서만 살짝 바뀌는 경우 등등에는 굳이 새로 그릴 필요가 없다.
마치 빙고 게임을 할 때 매 판마다 빙고판을 다시 만들 필요가 없는 것처럼 말이다.
notifyDataSetChanged는 어느 상황에서나 사용 가능하다는 장점이 있으나
퍼포먼스적인 측면에서 생각했을 때 앞으로 설명할 4가지 상황에 맞게 메서드를 사용해주는 것이 좋다.
3-2. 변경
3-2-1. notifyItemChanged
- notifyItemChanged(position: Int)
- notifyItemChanged(position: Int, payload: Any?)
- position: 변경된 아이템의 위치
- payload: 여기를 참고
과일을 주제로 빙고판을 채우고 있는데 실수로 채소를 1개 적었다고 치자.
굳이 빙고판을 다시 처음부터 그리고 단어를 채울 필요는 없을 것이다.
그냥 그 단어만 지우고 다시 적으면 되니까.
recyclerView도 마찬가지다.
어느 특정 위치의 아이템만 변경해야 한다면 notifyItemChanged를 사용하면 된다.
payload는 잘 설명되어 있는 블로그가 있어서 걸어둔 링크를 확인하면 될 것 같다.
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
참고로 notifyItemChanged는 notifyItemRangeChanged를 호출하는 메서드이다.
이때 아이템의 크기를 1로 설정하여 호출한다.
3-2-2. notifyItemRangeChanged
- notifyItemRangeChanged(positionStart: Int, itemCount: Int)
- notifyItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?)
- positionStart: 변경된 첫 번째 아이템의 위치
- itemCount: 변경된 아이템의 개수
- payload: [목차 3-2] 참고
변경된 아이템이 1개가 아니라 연속된 여러 개의 아이템이라면 이 메서드를 사용하면 된다.
0번 포지션 ~ 10번 포지션 이런 식으로 말이다.
변수는 첫번째 아이템의 위치와 변경된 아이템의 개수를 넘겨주면 된다.
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);
}
참고로 payload 값 안 넘겨주면 알아서 null을 넣어 호출한다.
3-3. 추가
3-3-1. notifyItemInserted
- notifyItemInserted(position: Int)
- position: 새로 삽입된 아이템의 위치
특정 위치에 아이템이 새로 삽입되었다면 이 메서드를 사용하면 된다.
주의할 점은 position은 0부터 시작한다는 것이다.
우리가 볼 때 리스트의 10번째 아이템은 position 값이 9라는 것이다.
3-3-2. notifyItemRangeInserted
- notifyItemRangeChanged(positionStart: Int, itemCount: Int)
- positionStart: 삽입된 첫 번째 아이템의 위치
- itemCount: 삽입된 아이템의 개수
연속된 여러 개의 아이템이 삽입될 때는 이 메서드를 사용하면 된다.
3-4. 삭제
3-4-1. notifyItemRemoved
- notifyItemRemoved(position: Int)
- position: 삭제된 아이템의 위치
추가와 메커니즘이 같아서 설명 생략.
특정한 아이템 1개를 삭제할 때 사용한다.
3-4-2. notifyItemRangeRemoved
- notifyItemRangeRemoved(positionStart: Int, itemCount: Int)
- positionStart: 삭제된 첫 번째 아이템의 위치
- itemCount: 삭제된 아이템의 개수
마찬가지이다. 연속된 여러 개의 아이템이 삭제될 때 사용.
3-5. 이동
3-5-1. notifyItemMoved
- notifyItemMoved(fromPosition: Int, toPosition: Int)
- fromPosition: 아이템의 이전 위치
- toPosition: 아이템의 새로운 위치
아이템이 이동했을 때 사용하는 메서드이다.
순위를 나타내는 리스트에서 특정 인물이 등수가 올랐다거나...
메모를 꾹 눌러 순서를 변경한다거나 등의 상황에서 사용한다.
4. 해결 방법
평소에 recyclerView를 사용할 때 나는 notifyDataSetChanged를 사용했었다.
양방향 바인딩을 같이 사용하면서 무슨 충돌이 일어난 건지, 아니면 내가 사소한 실수한 것이 사이드 이펙트를 만든 건지 정확한 원인은 찾진 못했지만 notifyItemRagnedChanged를 사용함으로써 문제를 해결할 수 있었다.
삽질을 하다가 아무튼 해결을 해서 기록으로 남기고 싶었고
notifyDataSetChanged를 무조건 남발하면 좋지 않다는 것과
다른 업데이트 방법들을 기록해두고 싶었다.
5. 또 다른 문제 및 해결 방법
notifyItemRangeChanged 메서드를 사용하니 움짤처럼 fade 효과가 들어갔다.
음... 나는 가독성이 떨어지는 것 같아서 애니메이션 효과를 없애고 싶었다.
굳이 이런 효과는... ;;;
recyclerView.itemAnimator = null
해결 방법은 간단하다.
recyclerView의 itemAnimator 속성에 null 값을 주면 된다.
레이아웃에서 설정하고 싶었는데 안 되는 듯하다.
클래스 파일에서 설정해주면 된다.
💡 느낀 점
- 내가 개발하는 앱 수준에서는 상황에 맞는 메서드를 사용하는 것이 퍼포먼스 측면에서 큰 차이를 보여주지는 않는 것 같다.
- 하지만 사용하는 메서드가 뭔지도 모르고 사용하는 것보단 알아두는 것이 좋다고 생각한다.
- 저런 증상이 생기는 원인을 끝내 못 찾은 게 너무 답답하다 😣
📘 참고한 자료
- payload 이해하기 - zerogdev님 블로그
- How to prevent RecyclerView item from blinking after notifyItemChanged? - stack overflow
- 변경 알림 메서드 정리 - 자빠질라님 블로그
'오늘은 뭘 배울까? > 삽질 기록' 카테고리의 다른 글
결제 구현 중 productDetailsList가 empty list를 받아오는 이유 (3) | 2023.11.02 |
---|---|
stateflow가 같은 값을 update 하는 이유 (0) | 2023.01.26 |
GitHub 토큰 인증 로그인 : support for password authentication was removed (2) | 2021.08.21 |
Observer가 LiveData를 관찰 못할 때 (0) | 2021.05.05 |
댓글