ListAdapter RecyclerView'daki öğeyi güncellemiyor


92

Yeni destek kitaplığını kullanıyorum ListAdapter. İşte adaptör için kodum

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

Adaptörü ile güncelliyorum

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

Dif sınıfım

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

Bağdaştırıcının tüm öğeleri oluşturduğu ilk kez submitList çağrıldığında olan şey, ancak güncellenmiş nesne özellikleriyle tekrar submitList çağrıldığında, değişen görünümü yeniden oluşturmuyor.

Listeyi kaydırırken görünümü yeniden oluşturuyor, bu da sırayla bindView()

Ayrıca, adapter.notifyDatasSetChanged()gönderme listesinden sonra çağırmanın görünümü güncellenmiş değerlerle oluşturduğunu fark ettim , ancak notifyDataSetChanged()liste bağdaştırıcısının yerleşik diff araçları olduğu için aramak istemiyorum

Biri bana yardım edebilir mi?


Sorun kendisiyle ArtistsDiffve dolayısıyla Artistkendisiyle ilgili olabilir .
tynn

Evet, ben de aynı şeyi düşünüyorum, ama tam olarak belirleyemiyorum
Veeresh Charantimath

Hata ayıklayabilir veya günlük ifadeleri ekleyebilirsiniz. Ayrıca ilgili kodu soruya ekleyebilirsiniz.
tynn

bu soruyu da kontrol edin, farklı bir şekilde
çözdüm

Yanıtlar:


107

Düzenleme: Bunun neden benim amacım olmadığını anlıyorum. Demek istediğim, en azından bir uyarı vermesi veya notifyDataSetChanged()işlevi çağırması gerektiğidir . Çünkü görünüşe göre submitList(...)işlevi bir sebepten dolayı çağırıyorum . İnsanların, submitList () 'in çağrıyı sessizce yoksaydığını anlayana kadar saatlerce neyin yanlış gittiğini anlamaya çalıştıklarından oldukça eminim.

Bunun nedeni Googletuhaf mantık. Yani aynı listeyi bağdaştırıcıya iletirseniz DiffUtil,.

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

ListAdapterAynı listedeki değişiklikleri idare edemezse, bunun tüm amacını gerçekten anlamıyorum . Geçiş yaptığınız listedeki öğeleri değiştirmek ve ListAdapterdeğişiklikleri görmek istiyorsanız, ya listenin derin bir kopyasını oluşturmanız ya da RecyclerViewkendi DiffUtillsınıfınızla normal kullanmanız gerekir .


5
Çünkü önceki durumun diff gerçekleştirmesini gerektirir. Elbette, önceki durumun üzerine yazarsanız, bunu kaldıramaz. O_o
EpicPandaForce

34
Evet ama o noktada, '' dememin bir nedeni var submitList, değil mi? En azından aramayı notifyDataSetChanged()sessizce görmezden gelmek yerine çağırmalıdır. İnsanların submitList()aramayı sessizce görmezden geldiklerini anlayana kadar saatlerce neyin yanlış gittiğini anlamaya çalıştıklarından oldukça eminim .
insa_c

6
Bu yüzden geri döndüm RecyclerView.Adapter<VH>ve notifyDataSetChanged(). Hayat şimdi iyidir. Çok fazla saat
boşa gitti

1
@insa_c Sayınıza 3 saat ekleyebilirsiniz, liste görünümümün bazı uç durumlarda neden güncellenmediğini anlamaya çalışırken ne kadar boşa harcadım ...
Bencri

1
notifyDataSetChanged()pahalıdır ve DiffUtil tabanlı bir uygulamaya sahip olma noktasını tamamen ortadan kaldırır. submitListYalnızca yeni verilerle arama yaparken dikkatli ve istekli olabilirsiniz , ancak bu gerçekten sadece bir performans tuzağıdır .
David Liu

63

Kitaplık, Room veya her güncellendiğinde yeni bir eşzamansız liste sunan başka bir ORM kullandığınızı varsayar, bu nedenle sadece submitList'i çağırmak işe yarayacaktır ve özensiz geliştiriciler için aynı liste çağrılırsa hesaplamaları iki kez yapmayı engeller.

Kabul edilen cevap doğru, açıklamayı sunuyor ama çözümü değil.

Bu tür kitaplıkları kullanmıyorsanız ne yapabilirsiniz?

submitList(null);
submitList(myList);

Başka bir çözüm, submitList'i (bu hızlı yanıp sönmeye neden olmaz) geçersiz kılmak olabilir:

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

Veya Kotlin koduyla:

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

Şüpheli mantık ama mükemmel çalışıyor. Tercih ettiğim yöntem ikincisidir çünkü her satırın bir onBind çağrısı almasına neden olmaz.


7
Bu bir hack. Sadece listenin bir kopyasını verin. .submitList(new ArrayList(list))
Paul Woitaschek

3
Son bir saati mantığımla ilgili sorunun ne olduğunu anlamaya çalışarak geçirdim. Ne kadar tuhaf bir mantık.
Jerry Okafor

7
@PaulWoitaschek Bu bir hack değil, JAVA kullanıyor :) geliştiricinin "uyuduğu" kütüphanelerdeki birçok sorunu çözmek için kullanılır. .SubmitList (yeni ArrayList (liste)) 'yi geçirmek yerine bunu seçmenizin nedeni, listeleri kodunuzda birden çok yere gönderebilmenizdir. Her seferinde yeni bir dizi oluşturmayı unutabilirsiniz, bu yüzden geçersiz kılarsınız.
RJFares

2
Room'u kullanırken bile benzer bir sorunla karşılaşıyorum.
Bink

2
Görünüşe göre bu, görünüm modelindeki bir listeyi yeni öğelerle güncellerken işe yarıyor, ancak bir listedeki bir öğenin bir özelliğini (boolean - isSelected) güncellediğimde bu işe yaramayacak .. Neden Idk ama DiffUtil benimle aynı eski ve yeni öğeyi döndürüyor kontrol ettim. Sorunun nerede ortaya çıkabileceği hakkında bir fikriniz var mı?
Ralph

23

Kotlin ile sadece listenizi yeni MutableList'e veya kullanımınıza göre başka bir listeye dönüştürmeniz gerekir.

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

Bu garip ama listeyi mutableList'e dönüştürmek benim için çalışıyor. Teşekkürler!
Thanh-Nhon Nguyen

4
Bu neden işe yarıyor? İşe yarıyor ama bunun neden olduğunu çok merak ediyorum.
Mart3Nisan4

Bana göre ListAdapter liste referansınız hakkında olmamalıdır, bu nedenle? .toMutableList () ile bağdaştırıcıya yeni bir örnek listesi gönderirsiniz. Umarım bu sizin için yeterince açıktır. @ Mart3Nisan4
Mina Samir

Teşekkürler. Yorumunuza göre, ListAdapter'ın veri setini bir List <T> biçimi olarak aldığını tahmin ettim, bu değişken bir liste veya hatta değişmez bir liste olabilir. Değişmez bir liste verirsem, yaptığım değişiklikler ListAdapter tarafından değil, veri kümesinin kendisi tarafından engelleniyor.
Mart3Nisan4

Ne var düşünüyorum @ March3April4 Ayrıca, aynı zamanda sahip sorumlulukları değiştirmek veya olmamalıdır listedeki öğeleri hesaplar çünkü diff programlarında olduğu kullanmak mekanizması hakkında bakım;)
Mina Samir

10

Benzer bir sorun yaşadım, ancak hatalı oluşturma setHasFixedSize(true)ve kombinasyonundan kaynaklandı android:layout_height="wrap_content". Adaptöre ilk kez boş bir liste verildi, böylece yükseklik hiç güncellenmedi ve güncellenmedi 0. Her neyse, bu benim sorunumu çözdü. Başka biri de aynı sorunu yaşayabilir ve bunun adaptörde sorun olduğunu düşünecektir.


1
Evet, geri dönüşümlü görünümü wrap_content olarak ayarlayın, listeyi eşleştirin, eşleştirin, bağdaştırıcı
çağırmaz

5

Bugün ben de bu "sorun" ile karşılaştım. Yardımıyla insa_c cevabı ve RJFares çözümüyle Kendime bir Kotlin uzatma fonksiyonu yaptı:

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

daha sonra her yerde kullanılabilir, örneğin:

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

ve yalnızca (ve her zaman) o anda yüklü olanla aynıysa listeyi kopyalar.


5

Kullanırken bazı sorunlarla karşılaşırsanız

recycler_view.setHasFixedSize(true)

bu yorumu kesinlikle kontrol etmelisiniz: https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531

Benim tarafımdaki sorunu çözdü.

(Burada, istenen yorumun bir ekran görüntüsü verilmiştir)

görüntü açıklamasını buraya girin


Bir çözüme bağlantı memnuniyetle karşılanır, ancak lütfen cevabınızın o olmadan yararlı olduğundan emin olun: Bağlantının etrafına bağlam ekleyin, böylece diğer kullanıcılarınız bunun ne olduğu ve neden orada olduğu konusunda fikir sahibi olurlar, ardından sayfanın en alakalı bölümünü alıntılayın ' hedef sayfanın mevcut olmaması durumunda yeniden bağlanma.
Mostafa Arian Nejad

3

Resmi belgelere göre :

SubmitList'i her çağırdığınızda, farklılaştırılması ve gösterilmesi için yeni bir liste gönderir .

Bu nedenle , önceki (önceden gönderilen liste) submitList'i her çağırdığınızda, Diff'i hesaplamaz ve veri kümesindeki değişiklik için adaptöre bildirimde bulunmaz .


3

Benim durumumda ben kurmayı unuttuğu LayoutManageriçin RecyclerView. Bunun etkisi yukarıda anlatılanla aynıdır.


2

Ben kullandıysa Benim için bu konu ortaya RecyclerViewiçini ait ScrollViewolannestedScrollingEnabled="false" ve RV yükseklik ayar için wrap_content.
Bağdaştırıcı düzgün bir şekilde güncellendi ve bağlama işlevi çağrıldı, ancak öğeler gösterilmedi - RecyclerVieworijinal boyutunda takılı kaldı.

Sorunu düzeltmek ScrollViewiçin değiştirme NestedScrollView.


2

Benzer bir problemim vardı. Sorun, Difföğeleri yeterince karşılaştırmayan işlevlerdeydi. Bu sorunu yaşayanlar, Diffişlevlerinizin (ve dolayısıyla veri nesnesi sınıflarınızın) uygun karşılaştırma tanımlarını içerdiğinden emin olun - yani, yeni öğede güncellenebilecek tüm alanları karşılaştırarak. Örneğin orijinal gönderide

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

Bu işlev (potansiyel olarak) etikette söylediği şeyi yapmaz: sınıfta işlevi geçersiz kılmadığınız sürece iki öğenin içeriğini karşılaştırmaz . Benim durumumda, onu uygularken gözetimimden dolayı gerekli alanlardan yalnızca birinin tanımını kontrol etmedim . Bu yapısal eşitliğe karşı referans eşitliktir, burada daha fazla bilgi bulabilirsiniz.equals()ArtistareContentsTheSame


1

Senaryosu benimkiyle aynı olan herkes için, neden işe yaradığını bilmediğim çözümümü burada bırakıyorum.

Benim için işe yarayan çözüm, listeyi değiştirilebilir bir liste olarak sunan @Mina Samir'den geldi.

Sorun senaryom:

-Bir parçanın içine arkadaş listesi yükleme.

  1. ActivityMain, FragmentFriendList'i ekler (arkadaş db öğelerinin yaşam verilerini gözlemler) ve aynı zamanda, tüm arkadaş listemi almak için sunucuya bir http isteği ister.

  2. Öğeleri http sunucusundan güncelleyin veya ekleyin.

  3. Her değişiklik, canlı verinin onChanged geri aramasını ateşler. Ancak, uygulamayı ilk kez başlattığımda, bu, masamda hiçbir şey olmadığı anlamına gelir, submitList herhangi bir hata olmadan başarılı olur, ancak ekranda hiçbir şey görünmez.

  4. Ancak, uygulamayı ikinci kez çalıştırdığımda, veriler ekrana yükleniyor.

Çözüm, yukarıda belirtildiği gibi, listeyi değişken liste olarak sunmaktır.


1

ListAdapter .submitlist'inizin çağrılmamasının nedeni, güncellediğiniz nesnenin bellekte aynı adresi tutmaya devam etmesidir.

Bir nesneyi .setText ile güncellediğinizde, orijinal nesnedeki değeri değiştirir.

Böylece, object.id == object2.id olup olmadığını kontrol ettiğinizde, aynı şekilde geri dönecektir, çünkü her ikisinin de bellekte aynı konuma bir referansı vardır.

Çözüm, güncellenmiş verilerle yeni bir nesne oluşturmak ve bunu listenize eklemektir. Daha sonra submitList çağrılacak ve düzgün çalışacaktır.


0

DifUtils'imi değiştirmem gerekiyordu

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

İçeriğin yeni olup olmadığını gerçekten döndürmek için, sadece modelin kimliğini karşılaştırmayın.


0

@RJFares'in ilk cevabının kullanılması listeyi başarıyla günceller, ancak kaydırma durumunu korumaz. Tüm RecyclerView0 konumundan başlar. Geçici bir çözüm olarak yaptığım şey şu:

   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

   }

Bu şekilde, kaydırma durumunu RecyclerViewdeğiştirilmiş verilerle birlikte koruyabiliyorum .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.