ListAdapter가 RecyclerView에서 항목을 업데이트하지 않음


89

새 지원 라이브러리를 사용하고 있습니다 ListAdapter. 다음은 어댑터의 코드입니다.

class ArtistsAdapter : ListAdapter<Artist, ArtistsAdapter.ViewHolder>(ArtistsDiff()) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(parent.inflate(R.layout.item_artist))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        fun bind(artist: Artist) {
            itemView.artistDetails.text = artist.artistAlbums
                    .plus(" Albums")
                    .plus(" \u2022 ")
                    .plus(artist.artistTracks)
                    .plus(" Tracks")
            itemView.artistName.text = artist.artistCover
            itemView.artistCoverImage.loadURL(artist.artistCover)
        }
    }
}

어댑터를 업데이트하고 있습니다.

musicViewModel.getAllArtists().observe(this, Observer {
            it?.let {
                artistAdapter.submitList(it)
            }
        })

내 diff 클래스

class ArtistsDiff : DiffUtil.ItemCallback<Artist>() {
    override fun areItemsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
        return oldItem?.artistId == newItem?.artistId
    }

    override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
        return oldItem == newItem
    }
}

어댑터가 모든 항목을 처음 렌더링 할 때 submitList가 호출 될 때 발생하는 일이 발생하지만, submitList가 업데이트 된 객체 속성으로 다시 호출 될 때 변경된 뷰를 다시 렌더링하지 않습니다.

목록을 스크롤하면 뷰가 다시 렌더링됩니다. bindView()

또한 adapter.notifyDatasSetChanged()제출 목록 후 호출 하면 업데이트 된 값으로 뷰가 렌더링되지만 notifyDataSetChanged()목록 어댑터에 diff 유틸리티가 내장되어 있으므로 호출하고 싶지 않습니다 .

누구든지 여기서 나를 도울 수 있습니까?


문제 ArtistsDiffArtist자체 구현과 관련이있을 수 있습니다 .
tynn

예, 저도 같은 생각하지만, 나는 핀 포인트로 보일 수 없다 그것
Veeresh Charantimath

디버그하거나 로그 문을 추가 할 수 있습니다. 또한 질문에 관련 코드를 추가 할 수 있습니다.
tynn

또한 내가 다르게 해결이 질문을 확인 stackoverflow.com/questions/58232606/...
MisterCat

답변:


100

편집 : 왜 이것이 내 요점이 아닌지 이해합니다. 내 요점은 최소한 경고를 주거나 notifyDataSetChanged()함수를 호출해야한다는 것 입니다. 분명히 submitList(...)이유 때문에 함수를 호출하고 있기 때문입니다. 나는 사람들이 submitList ()가 조용히 호출을 무시한다는 것을 알아낼 때까지 몇 시간 동안 무엇이 잘못되었는지 알아 내려고 노력하고 있다고 확신합니다.

이것은 Google이상한 논리 때문입니다 . 따라서 동일한 목록을 어댑터에 전달하면 DiffUtil.

public void submitList(final List<T> newList) {
    if (newList == mList) {
        // nothing to do
        return;
    }
....
}

ListAdapter동일한 목록에서 변경 사항을 처리 할 수 ​​없다면 이 모든 내용을 이해하지 못합니다 . 목록의 항목을 변경하고 ListAdapter변경 사항을 확인하려면 목록의 전체 복사본을 만들어야하거나 RecyclerView자신의 DiffUtill클래스에 일반을 사용해야 합니다 .


5
diff를 수행하려면 이전 상태가 필요하기 때문입니다. 물론 이전 상태를 덮어 쓰면 처리 할 수 ​​없습니다. O_o
EpicPandaForce

29
예,하지만 그 시점에서 제가라고 부르는 이유가 submitList있습니다. notifyDataSetChanged()조용히 전화를 무시하는 대신 최소한를 호출해야합니다 . 나는 사람들이 submitList()조용히 전화를 무시할 때까지 몇 시간 동안 무엇이 잘못되었는지 파악하려고 노력하고 있다고 확신 합니다.
insa_c

5
그래서 다시 RecyclerView.Adapter<VH>notifyDataSetChanged(). 이제 삶은 좋습니다. 시간 낭비
Udayaditya Barua

@insa_c 당신은 당신의 카운트에 3 시간을 더할 수있다. 그것은 내가 왜 내리스트 뷰가 몇몇 엣지 케이스에서 업데이트되지 않았는지 이해하려고 애 쓰느라 낭비한 양이다 ...
Bencri

notifyDataSetChanged()비용이 많이 들고 DiffUtil 기반 구현의 요점을 완전히 무력화합니다. submitList새 데이터로만 호출하는 데주의하고 의도적 일 수 있지만 실제로는 성능 함정일뿐입니다.
David Liu

62

라이브러리는 업데이트 될 때마다 새로운 비동기 목록을 제공하는 Room 또는 다른 ORM을 사용하고 있다고 가정하므로 submitList를 호출하는 것만으로도 작동하며 엉성한 개발자의 경우 동일한 목록이 호출되면 계산을 두 번 수행하지 않습니다.

받아 들여지는 대답은 정확하며 설명은 제공하지만 해결책은 제공하지 않습니다.

이러한 라이브러리를 사용하지 않는 경우 수행 할 수있는 작업은 다음과 같습니다.

submitList(null);
submitList(myList);

또 다른 해결책은 submitList (빠른 깜박임을 유발하지 않음)를 다음과 같이 재정의하는 것입니다.

@Override
public void submitList(final List<Author> list) {
    super.submitList(list != null ? new ArrayList<>(list) : null);
}

또는 Kotlin 코드 :

override fun submitList(list: List<CatItem>?) {
    super.submitList(list?.let { ArrayList(it) })
}

의심스러운 논리이지만 완벽하게 작동합니다. 내가 선호하는 방법은 각 행이 onBind 호출을받지 않기 때문에 두 번째 방법입니다.


4
그것은 해킹입니다. 목록의 사본을 전달하십시오. .submitList(new ArrayList(list))
Paul Woitaschek

2
나는 내 논리에 문제가 무엇인지 파악하기 위해 지난 시간을 보냈다. 그런 이상한 논리.
Jerry Okafor

7
@PaulWoitaschek 이것은 해킹이 아닙니다. 이것은 JAVA를 사용하고 있습니다. :) 개발자가 "잠자고있는"라이브러리의 많은 문제를 해결하는 데 사용됩니다. .submitList (new ArrayList (list))를 전달하는 대신 이것을 선택하는 이유는 코드의 여러 위치에 목록을 제출할 수 있기 때문입니다. 매번 새 어레이를 만드는 것을 잊을 수 있으므로 재정의하는 것입니다.
RJFares

1
@ Po10cio 그렇게 썼을 때 매번 새로운 목록을 제공하는 ORM 라이브러리에서만 사용할 것이라고 가정했기 때문에 주로 이상합니다. 동일한 목록을 통과했지만 업데이트 된 경우에는 그 문제를 해결해야합니다. 이것이 가장 좋은 방법입니다
RJFares

1
Room을 사용하더라도 비슷한 문제가 발생합니다.
Bink

20

Kotlin을 사용하면 목록을 이와 같은 새로운 MutableList 또는 용도에 따라 다른 유형의 목록 으로 변환하면 됩니다.

.observe(this, Observer {
            adapter.submitList(it?.toMutableList())
        })

그것은 이상하지만 목록을 mutableList로 변환하는 것이 저에게 효과적입니다. 감사!
Thanh-Nhon Nguyen

3
도대체 왜이게 효과가 있니? 작동하지만 왜 이런 일이 발생하는지 매우 궁금합니다.
March3April4

제 생각에는 ListAdapter는 목록 참조에 대해 말하지 않아야하므로 새 인스턴스 목록을 어댑터에 제출하십시오. 나는 당신을 위해 충분히 명확하기를 바랍니다. @ March3April4
Mina Samir

감사. 귀하의 의견에 따르면 ListAdapter가 List <T>의 형태로 데이터 세트를 수신한다고 생각했습니다. List <T>는 변경 가능한 목록 또는 변경 불가능한 목록 일 수 있습니다. 변경 불가능한 목록을 전달하면 변경 사항이 ListAdapter가 아닌 데이터 세트 자체에 의해 차단됩니다.
March3April4

나는 당신이 그것을 얻었다 고 생각한다 @ March3April4 또한, 그것은 또한 책임이 있기 때문에 diff utils와 함께 사용하는 메커니즘에 대해 관심이 있기 때문에 목록의 항목이 변경되어야하는지 여부를 계산해야합니다;)
Mina Samir

9

비슷한 문제가 있었지만 잘못된 렌더링은 setHasFixedSize(true)및 의 조합으로 인해 발생했습니다 android:layout_height="wrap_content". 처음으로 어댑터에 빈 목록이 제공되었으므로 높이가 업데이트되지 않고 0. 어쨌든 이것은 내 문제를 해결했습니다. 다른 사람이 동일한 문제를 가지고있을 수 있으며 어댑터의 문제라고 생각할 것입니다.


1
예, recycleview를 wrap_content로 설정하면 목록이 업데이트됩니다. match_parent로 설정하면 어댑터가 호출되지 않습니다.
Exel Staderlin

5

사용할 때 문제가 발생하는 경우

recycler_view.setHasFixedSize(true)

https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531 이 댓글을 확실히 확인해야합니다.

그것은 내 편에서 문제를 해결했습니다.

(여기에 요청 된 댓글의 스크린 샷이 있습니다.)

여기에 이미지 설명 입력


솔루션에 대한 링크는 환영하지만 답변이없는 경우에도 유용한 지 확인하십시오. 링크 주변에 컨텍스트를 추가하여 동료 사용자가 그것이 무엇이고 왜 존재하는지 알 수 있도록 한 다음 페이지에서 가장 관련성이 높은 부분을 인용하십시오. 대상 페이지를 사용할 수없는 경우 다시 연결합니다.
Mostafa Arian Nejad

4

오늘 나는 또한이 "문제"를 발견했다. 의 도움으로 insa_c의 대답RJFares의 솔루션 나 자신에게 코 틀린 확장 기능을했다 :

/**
 * Update the [RecyclerView]'s [ListAdapter] with the provided list of items.
 *
 * Originally, [ListAdapter] will not update the view if the provided list is the same as
 * currently loaded one. This is by design as otherwise the provided DiffUtil.ItemCallback<T>
 * could never work - the [ListAdapter] must have the previous list if items to compare new
 * ones to using provided diff callback.
 * However, it's very convenient to call [ListAdapter.submitList] with the same list and expect
 * the view to be updated. This extension function handles this case by making a copy of the
 * list if the provided list is the same instance as currently loaded one.
 *
 * For more info see 'RJFares' and 'insa_c' answers on
 * /programming/49726385/listadapter-not-updating-item-in-reyclerview
 */
fun <T, VH : RecyclerView.ViewHolder> ListAdapter<T, VH>.updateList(list: List<T>?) {
    // ListAdapter<>.submitList() contains (stripped):
    //  if (newList == mList) {
    //      // nothing to do
    //      return;
    //  }
    this.submitList(if (list == this.currentList) list.toList() else list)
}

그런 다음 어디서나 사용할 수 있습니다. 예 :

viewModel.foundDevices.observe(this, Observer {
    binding.recyclerViewDevices.adapter.updateList(it)
})

현재로드 된 목록과 동일한 경우에만 (그리고 항상) 목록을 복사합니다.


3

공식 문서 에 따르면 :

submitList호출 할 때마다 비교 및 표시 할 새 목록을 제출합니다 .

이유는 당신이 submitList를 호출 할 때마다이전 (이미 제출 목록) 는 DIFF를 계산하지 않습니다 및 않습니다 어댑터를 통지하지 데이터 집합의 변경.


2

나를 위해이 문제는 RecyclerView내부를 ScrollView사용 nestedScrollingEnabled="false"하고 RV 높이를 wrap_content.
어댑터가 제대로 업데이트되고 바인드 기능이 호출되었지만 항목이 표시되지 않고 RecyclerView원래 크기로 고정되었습니다.

변경 ScrollViewNestedScrollView고정 문제.


2

제 경우 LayoutManager에는 RecyclerView. 그 효과는 위에서 설명한 것과 같습니다.


1

시나리오가 나와 동일한 사람을 위해 솔루션을 남겨 두었습니다. 왜 작동하는지 모르겠습니다.

나를 위해 일한 솔루션은 목록을 변경 가능한 목록으로 제출하는 @Mina Samir에서 왔습니다.

내 문제 시나리오 :

-조각 안에 친구 목록을로드합니다.

  1. ActivityMain은 FragmentFriendList (친구 DB 항목의 라이브 데이터에 관찰)를 첨부하고 동시에 내 친구 목록을 모두 가져 오기 위해 서버에 http 요청을 요청합니다.

  2. http 서버에서 항목을 업데이트하거나 삽입하십시오.

  3. 모든 변경은 라이브 데이터의 onChanged 콜백을 시작합니다. 그러나 처음으로 응용 프로그램을 시작하면 내 테이블에 아무것도 없다는 것을 의미합니다. submitList는 어떤 종류의 오류도없이 성공하지만 화면에는 아무것도 나타나지 않습니다.

  4. 하지만 두 번째로 애플리케이션을 실행하면 데이터가 화면에로드됩니다.

해결책은 위에서 언급했듯이 목록을 mutableList로 제출하는 것입니다.


1

비슷한 문제가있었습니다. 문제는 Diff항목을 적절히 비교하지 않은 기능에 있었습니다 . 이 문제가있는 사람은 Diff함수 (및 확장으로 데이터 개체 클래스)에 적절한 비교 정의가 포함되어 있는지 확인하십시오. 즉, 새 항목에서 업데이트 될 수있는 모든 필드를 비교합니다. 예를 들어 원래 게시물에서

    override fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean {
    return oldItem == newItem
}

이 함수는 (잠재적으로) 레이블에 표시된대로 수행하지 않습니다 . 클래스 에서 함수를 재정의 하지 않는 한 두 항목의 내용을 비교하지 않습니다 . 제 경우에는 구현할 때 감독이 있었기 때문에 필요한 필드 중 하나만 확인 하고 정의하지 않았습니다 . 이것은 구조적 평등 대 참조 평등입니다. 여기에서 자세한 내용을 확인할 수 있습니다.equals()ArtistareContentsTheSame


0

내 DiffUtils를 수정해야했습니다.

override fun areContentsTheSame(oldItem: Vehicle, newItem: Vehicle): Boolean {

콘텐츠가 새로운 지 여부를 실제로 반환하려면 모델의 ID를 비교하는 것이 아닙니다.


0

@RJFares 첫 번째 답변을 사용하면 목록이 성공적으로 업데이트되지만 스크롤 상태는 유지되지 않습니다. 전체 RecyclerView는 0 번째 위치에서 시작합니다. 해결 방법은 다음과 같습니다.

   fun updateDataList(newList:List<String>){ //new list from DB or Network

     val tempList = dataList.toMutableList() // dataList is the old list
     tempList.addAll(newList)
     listAdapter.submitList(tempList) // Recyclerview Adapter Instance
     dataList = tempList

   }

이렇게하면 RecyclerView수정 된 데이터와 함께 스크롤 상태를 유지할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.