ListAdapterがRecyclerViewのアイテムを更新していません


92

新しいサポートライブラリを使用しています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 utilsが組み込まれているため、呼び出したくありません。

誰かがここで私を助けることができますか?


問題はArtistsDiffArtistそれ自体の実装に関連している可能性があります。
tynn 2018

はい、私も同じだと思いますが、それを
正確に特定

デバッグするか、ログステートメントを追加できます。また、関連するコードを質問に追加することもできます。
tynn 2018

また、この質問をチェックし、私はそれが違っ解決stackoverflow.com/questions/58232606/...
MisterCat

回答:


106

編集:私はこれがなぜ起こるのか理解していますが、それは私のポイントではありませんでした。私のポイントは、少なくとも警告を出すか、notifyDataSetChanged()関数を呼び出す必要があるということです。どうやら私はsubmitList(...)理由で関数を呼び出しているからです。submitList()が呼び出しを黙って無視することを理解するまで、人々は何時間も何が悪かったのかを理解しようとしていると確信しています。

これはGoogle奇妙な論理によるものです。したがって、同じリストをアダプタに渡すと、は呼び出されませんDiffUtil

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

ListAdapter同じリストの変更を処理できない場合、私はこれの全体的なポイントを本当に理解していません。に渡すリストの項目を変更して変更ListAdapterを確認する場合は、リストのディープコピーを作成するかRecyclerView、独自のDiffUtillクラスで通常を使用する必要があります。


5
差分を実行するには前の状態が必要だからです。もちろん、前の状態を上書きすると処理できません。O_O
EpicPandaForce

34
はい、でもその時点で、私がと呼ぶ理由がありsubmitListますよね?呼び出しnotifyDataSetChanged()を黙って無視するのではなく、少なくとも呼び出す必要があります。私は人々が何時間も何が悪かったのかを理解しようとしていると確信submitList()しています。
insa_c

6
私は戻っていますので、RecyclerView.Adapter<VH>notifyDataSetChanged()。ライフは今は良いです。かなりの時間を無駄にした
UdayadityaBarua20年

1
@insa_cカウントに3時間を追加できます。これは、一部のエッジケースでリストビューが更新されなかった理由を理解しようとして無駄になりました...
Bencri 2020年

1
notifyDataSetChanged()高価であり、DiffUtilベースの実装を持つという点を完全に打ち負かします。submitList新しいデータのみを使用して呼び出す場合は注意深く意図的に行うことができますが、実際にはそれは単なるパフォーマンスの罠です。
デヴィッド・劉

61

ライブラリは、更新されるたびに新しい非同期リストを提供するRoomまたはその他のORMを使用していることを前提としているため、submitListを呼び出すだけで機能し、ずさんな開発者の場合、同じリストが呼び出された場合に2回計算を行うことを防ぎます。

受け入れられた答えは正しいです、それは説明を提供しますが、解決策を提供しません。

このようなライブラリを使用していない場合にできることは次のとおりです。

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) })
}

疑わしいロジックですが、完全に機能します。私が好む方法は2番目の方法です。これは、各行がonBind呼び出しを取得しないためです。


7
それはハックです。リストのコピーを渡すだけです。.submitList(new ArrayList(list))
Paul Woitaschek 2018

3
私はこの1時間、自分のロジックの問題を理解しようと努めてきました。そのような奇妙な論理。
Jerry Okafor

7
@PaulWoitaschekこれはハックではありません。これはJAVAを使用しています:)開発者が「眠っている」ライブラリの多くの問題を修正するために使用されます。.submitList(new ArrayList(list))を渡す代わりにこれを選択する理由は、コード内の複数の場所でリストを送信できるためです。毎回新しい配列を作成するのを忘れる可能性があるため、オーバーライドします。
RJFares 2018年

2
Roomを使用していても、同様の問題が発生しています。
ビンク2018

2
どうやらこれはviewmodelのリストを新しいアイテムで更新するときに機能しますが、リスト内のアイテムのプロパティ(boolean --isSelected)を更新しても、これはまだ機能しません。チェックしました。問題が発生する可能性のある場所はありますか?
ラルフ

22

Kotlinを使用すると、使用状況に応じて、リストをこのような新しいMutableListまたは別のタイプのリストに変換する必要があります。

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

それは奇妙ですが、リストをmutableListに変換することは私にとってはうまくいきます。ありがとう!
Thanh-NhonNguyen20年

4
なぜこれが機能しているのですか?それは機能しますが、なぜこれが起こるのか非常に興味があります。
March3April4 2020

私の意見では、ListAdapterはリスト参照を考慮してはならないので、それを使用して、新しいインスタンスリストをアダプタに送信します。私はあなたのためにそれが十分に明確であることを願っています。March3April4 @
ミナサミル

ありがとう。あなたのコメントによると、ListAdapterはそのデータセットをList <T>の形式として受け取ると推測しました。これは、可変リスト、または不変リストの場合もあります。不変のリストを渡すと、行った変更は、ListAdapterではなく、データセット自体によってブロックされます。
March3April4

@ March3April4また、diff utilsで使用するメカニズムにも注意してください。これには、リスト内の項目が変更されるかどうかを計算する責任もあるためです;)
MinaSamir20年

10

私は同様の問題があったが、間違ったレンダリングはの組み合わせによって引き起こされたsetHasFixedSize(true)android:layout_height="wrap_content"。初めてアダプタに空のリストが提供されたため、高さは更新されず、でした0。とにかく、これは私の問題を解決しました。他の誰かが同じ問題を抱えている可能性があり、それがアダプターの問題であると考えるでしょう。


1
ええ、recycleviewをwrap_contentに設定するとリストが更新されます。match_parentに設定すると、アダプターは呼び出されません
ExelStaderlin20年

5

今日、私もこの「問題」に出くわしました。insa_cの回答RJFaresのソリューション助けを借りて、私は自分自身をKotlin拡張関数にしました。

/**
 * 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)
})

そして、それが現在ロードされているものと同じである場合にのみ(そして常に)リストをコピーします。


5

使用中に問題が発生した場合

recycler_view.setHasFixedSize(true)

このコメントを確実に確認する必要があります:https//github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531

それは私の側の問題を解決しました。

(これは要求されたコメントのスクリーンショットです)

ここに画像の説明を入力してください


ソリューションへのリンクは大歓迎ですが、それがなくても回答が役立つことを確認してください。リンクの周りにコンテキストを追加して、他のユーザーがそれが何であるか、なぜそこにあるのかを理解できるようにしてから、ページの最も関連性の高い部分を引用してください。ターゲットページが利用できない場合に再リンクします。
Mostafa ArianNejad19年

3

公式ドキュメントによると:

submitList呼び出すと、diffして表示する新しいリストが送信されます。

これが、前の(すでに送信されたリスト)でsubmitListを呼び出すたびに、差分計算、データセットの変更についてアダプター通知ない理由です。


3

私の場合、のを設定するのを忘れLayoutManagerましたRecyclerView。その効果は上記と同じです。


2

私の場合、この問題は、RVの高さをに設定してRecyclerView内部を使用している場合に発生しました。 アダプターが正しく更新され、バインド関数が呼び出されましたが、アイテムは表示されませんでした。元のサイズでスタックしていました。ScrollViewnestedScrollingEnabled="false"wrap_content
RecyclerView

問題ScrollViewNestedScrollView修正するために変更します。


2

私も同様の問題を抱えていました。問題はDiff機能にあり、アイテムを適切に比較していませんでした。この問題を抱えている人は、Diff関数(ひいてはデータオブジェクトクラス)に適切な比較定義が含まれていることを確認してください。つまり、新しいアイテムで更新される可能性のあるすべてのフィールドを比較します。たとえば、元の投稿で

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

この関数は(潜在的に)ラベルに記載されていることを実行しません。クラス内の関数をオーバーライドしない限り、2つの項目の内容を比較しません。私の場合、実装時の見落としにより、必要なフィールドの1つだけをチェックしました。これは構造的平等と参照的平等です。詳細については、こちらをご覧ください。equals()ArtistareContentsTheSame


1

シナリオが私のものと同じである人のために、私は私の解決策を残しますが、なぜそれが機能しているのかわかりません。

私のために働いた解決策は、リストを可変リストとして提出している@MinaSamirからのものでした。

私の問題のシナリオ:

-フラグメント内の友達リストをロードします。

  1. ActivityMainは、FragmentFriendList(フレンドデータベースアイテムのライブデータを監視)をアタッチすると同時に、サーバーにhttpリクエストを要求して、すべてのフレンドリストを取得します。

  2. httpサーバーからアイテムを更新または挿入します。

  3. すべての変更は、livedataのonChangedコールバックを点火します。しかし、アプリケーションを初めて起動したとき、つまりテーブルに何もなかったとき、submitListは何のエラーもなく成功しますが、画面には何も表示されません。

  4. ただし、アプリケーションを起動するのが2回目になると、データが画面に読み込まれます。

解決策は、上記のように、リストをmutableListとして送信することです。


1

ListAdapter .submitlistが呼び出されない理由は、更新したオブジェクトがまだ同じアドレスをメモリに保持しているためです。

たとえば.setTextでオブジェクトを更新すると、元のオブジェクトの値が変更されます。

そのため、object.id == object2.idかどうかを確認すると、どちらもメモリ内の同じ場所への参照があるため、同じものとして返されます。

解決策は、更新されたデータを使用して新しいオブジェクトを作成し、それをリストに挿入することです。次に、submitListが呼び出され、正しく機能します


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.