본문 바로가기
오늘은 뭘 배울까?/삽질 기록

recyclerView를 업데이트 하는 5가지 방법 (notifyDataSetChanged를 사용하지 말자)

by Kim Juhwan 2021. 6. 18.

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배속으로 움짤을 만들었다. 피규어는 심심해서...

 

문제가 되는 부분이 너무 순식간에 지나가서 슬로우 모션 촬영에다가 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를 사용한 모습 (슬로우 모션 + 0.2 배속)

 

notifyItemRangeChanged 메서드를 사용하니 움짤처럼 fade 효과가 들어갔다.

음... 나는 가독성이 떨어지는 것 같아서 애니메이션 효과를 없애고 싶었다.

굳이 이런 효과는... ;;;

 

recyclerView.itemAnimator = null

해결 방법은 간단하다.

recyclerView의 itemAnimator 속성에 null 값을 주면 된다.

레이아웃에서 설정하고 싶었는데 안 되는 듯하다.

클래스 파일에서 설정해주면 된다.

 


💡 느낀 점

  • 내가 개발하는 앱 수준에서는 상황에 맞는 메서드를 사용하는 것이 퍼포먼스 측면에서 큰 차이를 보여주지는 않는 것 같다.
  • 하지만 사용하는 메서드가 뭔지도 모르고 사용하는 것보단 알아두는 것이 좋다고 생각한다.
  • 저런 증상이 생기는 원인을 끝내 못 찾은 게 너무 답답하다 😣

📘 참고한 자료


 

반응형

댓글