Estou usando a nova biblioteca de suporte ListAdapter
. Aqui está meu código para o adaptador
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)
}
}
}
Estou atualizando o adaptador com
musicViewModel.getAllArtists().observe(this, Observer {
it?.let {
artistAdapter.submitList(it)
}
})
Minha classe 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
}
}
O que está acontecendo é quando submitList é chamado na primeira vez que o adaptador renderiza todos os itens, mas quando submitList é chamado novamente com propriedades de objeto atualizadas, ele não renderiza novamente a visualização que foi alterada.
Ele renderiza novamente a visualização conforme eu rolar a lista, que por sua vez chama bindView()
Além disso, percebi que chamar adapter.notifyDatasSetChanged()
após enviar lista renderiza a visualização com valores atualizados, mas não quero chamar notifyDataSetChanged()
porque o adaptador de lista tem utilitários diff integrados
Alguém pode me ajudar aqui?
fonte
ArtistsDiff
e, portanto, à implementação deArtist
si mesmo.Respostas:
Edit: Eu entendo por que isso acontece, não era meu ponto. Meu ponto é que pelo menos precisa dar um aviso ou chamar a
notifyDataSetChanged()
função. Porque aparentemente estou chamando asubmitList(...)
função por um motivo. Tenho certeza de que as pessoas estão tentando descobrir o que deu errado por horas até descobrirem que submitList () ignora a chamada silenciosamente.Isso ocorre por causa de
Google
uma lógica estranha. Portanto, se você passar a mesma lista para o adaptador, ele nem chamará oDiffUtil
.public void submitList(final List<T> newList) { if (newList == mList) { // nothing to do return; } .... }
Eu realmente não entendo todo o sentido disso,
ListAdapter
se ele não pode lidar com mudanças na mesma lista. Se você deseja alterar os itens da lista que você passa para oListAdapter
e ver as alterações, então você precisa criar uma cópia profunda da lista ou usar regularRecyclerView
com sua própriaDiffUtill
classe.fonte
submitList
, certo? Deve pelo menos chamar o emnotifyDataSetChanged()
vez de ignorar silenciosamente a chamada. Tenho certeza de que as pessoas estão tentando descobrir o que deu errado por horas até que eles descubram osubmitList()
ignora silenciosamente a ligação.RecyclerView.Adapter<VH>
enotifyDataSetChanged()
. A vida está boa agora. Boa quantidade de horasnotifyDataSetChanged()
é caro e derrotaria completamente o ponto de ter uma implementação baseada no DiffUtil. Você pode ser cuidadoso e atento ao chamarsubmitList
apenas com novos dados, mas na verdade isso é apenas uma armadilha de desempenho.A biblioteca assume que você está usando o Room ou qualquer outro ORM que oferece uma nova lista assíncrona toda vez que for atualizada, então, apenas chamar submitList nela funcionará e, para desenvolvedores desleixados, evita fazer os cálculos duas vezes se a mesma lista for chamada.
A resposta aceita é correta, oferece a explicação, mas não a solução.
O que você pode fazer caso não esteja usando nenhuma dessas bibliotecas é:
submitList(null); submitList(myList);
Outra solução seria substituir submitList (o que não causa aquela piscada rápida) como tal:
@Override public void submitList(final List<Author> list) { super.submitList(list != null ? new ArrayList<>(list) : null); }
Ou com código Kotlin:
override fun submitList(list: List<CatItem>?) { super.submitList(list?.let { ArrayList(it) }) }
Lógica questionável, mas funciona perfeitamente. Meu método preferido é o segundo porque ele não faz com que cada linha receba uma chamada onBind.
fonte
.submitList(new ArrayList(list))
com Kotlin você só precisa converter sua lista para uma nova MutableList como esta ou outro tipo de lista de acordo com seu uso
.observe(this, Observer { adapter.submitList(it?.toMutableList()) })
fonte
Tive um problema semelhante, mas a renderização incorreta foi causada por uma combinação de
setHasFixedSize(true)
eandroid:layout_height="wrap_content"
. Pela primeira vez, o adaptador foi fornecido com uma lista vazia, de modo que a altura nunca foi atualizada e foi0
. Enfim, isso resolveu meu problema. Outra pessoa pode ter o mesmo problema e pensar que é um problema no adaptador.fonte
Hoje também me deparei com esse "problema". Com a ajuda da resposta do insa_c e da solução da RJFares, criei uma função de extensão do 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) }
que pode ser usado em qualquer lugar, por exemplo:
viewModel.foundDevices.observe(this, Observer { binding.recyclerViewDevices.adapter.updateList(it) })
e ele apenas (e sempre) copia a lista se for a mesma que está carregada no momento.
fonte
Se você encontrar alguns problemas ao usar
recycler_view.setHasFixedSize(true)
você definitivamente deve verificar este comentário: https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531
Resolveu o problema do meu lado.
(Aqui está uma captura de tela do comentário, conforme solicitado)
fonte
De acordo com os documentos oficiais :
Sempre que você chama submitList, ele envia uma nova lista para ser analisada e exibida.
É por isso que sempre que você chama submitList na lista anterior (lista já enviada), ele não calcula o Diff e não notifica o adaptador sobre a mudança no conjunto de dados.
fonte
No meu caso, esqueci de definir o
LayoutManager
para oRecyclerView
. O efeito disso é o mesmo descrito acima.fonte
Para mim, esta questão parecia se eu estava usando
RecyclerView
dentro deScrollView
comnestedScrollingEnabled="false"
e conjunto de altura RVwrap_content
.O adaptador foi atualizado corretamente e a função de ligação foi chamada, mas os itens não foram mostrados - o
RecyclerView
estava preso em seu 'tamanho original.Mudar
ScrollView
paraNestedScrollView
corrigiu o problema.fonte
Eu tive um problema parecido. O problema estava nas
Diff
funções, que não comparavam adequadamente os itens. Qualquer pessoa com esse problema, certifique-se de que suasDiff
funções (e, por extensão, suas classes de objetos de dados) contenham definições de comparação adequadas - isto é, comparando todos os campos que podem ser atualizados no novo item. Por exemplo na postagem originaloverride fun areContentsTheSame(oldItem: Artist?, newItem: Artist?): Boolean { return oldItem == newItem }
Esta função (potencialmente) não faz o que diz no rótulo: ela não compara o conteúdo dos dois itens - a menos que você tenha sobrescrito a
equals()
função naArtist
classe. No meu caso, não o fiz, e a definição deareContentsTheSame
apenas verifiquei um dos campos necessários, devido ao meu descuido na implementação. Isso é igualdade estrutural vs. igualdade referencial, você pode encontrar mais sobre isso aquifonte
Para quem o cenário for igual ao meu, deixo aqui a minha solução, que não sei porque está a funcionar.
A solução que funcionou para mim foi da @Mina Samir, que está enviando a lista como uma lista mutável.
Meu cenário de problema:
-Carregando uma lista de amigos dentro de um fragmento.
ActivityMain anexa o FragmentFriendList (Observa os dados vividos de itens de banco de dados de amigos) e, ao mesmo tempo, solicita uma solicitação http ao servidor para obter toda a minha lista de amigos.
Atualize ou insira os itens do servidor http.
Cada mudança acende o retorno de chamada onChanged do liveata. Mas, quando é a primeira vez que inicio o aplicativo, o que significa que não havia nada na minha mesa, a submitList é bem-sucedida sem nenhum tipo de erro, mas nada aparece na tela.
No entanto, quando é a segunda vez que inicio o aplicativo, os dados estão sendo carregados na tela.
A solução é, conforme mencionado acima, submeter a lista como uma mutableList.
fonte
O motivo pelo qual ListAdapter .submitlist não é chamado é porque o objeto que você atualizou ainda mantém o mesmo endereço na memória.
Quando você atualiza um objeto com, digamos .setText, ele altera o valor do objeto original.
Assim, quando você verificar se object.id == object2.id, ele retornará como o mesmo porque ambos têm uma referência ao mesmo local na memória.
A solução é criar um novo objeto com os dados atualizados e inseri-lo em sua lista. Então submitList será chamado e funcionará corretamente
fonte
Eu precisava modificar meus DiffUtils
override fun areContentsTheSame(oldItem: Vehicle, newItem: Vehicle): Boolean {
Para realmente retornar se o conteúdo é novo, não basta comparar o id do modelo.
fonte
Usar a primeira resposta @RJFares atualiza a lista com sucesso, mas não mantém o estado de rolagem. O todo
RecyclerView
começa na 0ª posição. Como alternativa, fiz o seguinte: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 }
Dessa forma, posso manter o estado de rolagem
RecyclerView
junto com os dados modificados.fonte