ListAdapter не обновляет элемент в RecyclerView

Я использую новую библиотеку 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 встроенный

Может ли кто-нибудь помочь мне здесь?

Ответ 1

Редактирование: Я понимаю, почему это происходит, и это не мое дело. Я notifyDataSetChanged() что он должен хотя бы дать предупреждение или вызвать notifyDataSetChanged(). Потому что, по-видимому, я submitList(...) функцию submitList(...) по какой-то причине. Я уверен, что люди пытаются выяснить, что пошло не так в течение нескольких часов, пока они не выяснят, что submitList() молча игнорирует вызов.

Это из-за необычной логики Google. Поэтому, если вы передаете один и тот же список в адаптер, он даже не вызывает DiffUtil.

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

Я действительно не понимаю ListAdapter этого ListAdapter если он не может обрабатывать изменения в том же списке. Если вы хотите изменить элементы в списке, которые вы переходите в ListAdapter и видите изменения, вам необходимо создать глубокую копию списка или вам нужно использовать обычный RecyclerView со своим собственным классом DiffUtill.

Ответ 2

В библиотеке предполагается, что вы используете Room или любой другой ORM, который предлагает новый список асинхронов каждый раз, когда он обновляется, поэтому просто вызов функции submitList на нем будет работать, а для неаккуратных разработчиков он предотвращает выполнение вычислений дважды, если вызывается тот же список.

Принятый ответ правильный, он предлагает объяснение, но не решение.

Что вы можете сделать, если вы не используете такие библиотеки:

submitList(null);
submitList(myList);

Другим решением было бы переопределить submitList (который не вызывает быстрого мигания) как таковой:

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

Сдержанная логика, но работает отлично. Мой предпочтительный метод - второй, потому что он не приводит к тому, что каждая строка получает вызов onBind.

Ответ 3

У меня была похожая проблема, но неправильное отображение было вызвано комбинацией setHasFixedSize(true) и android:layout_height="wrap_content". Впервые адаптер был снабжен пустым списком, поэтому высота не обновлялась и была 0. Во всяком случае, это решило мою проблему. Кто-то другой может иметь такую же проблему и подумает, что это проблема в адаптере.

Ответ 4

Сегодня я тоже наткнулся на эту "проблему". С помощью ответа insa_c и решения RJFares я сделал себе функцию расширения Kotlin:

/**
 * Update the [RecyclerView] [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 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
 * https://stackoverflow.com/questions/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

Согласно официальным документам:

Всякий раз, когда вы вызываете submitList, он отправляет новый список для отображения и отображения.

Вот почему всякий раз, когда вы вызываете submitList для предыдущего (уже отправленного списка), он не вычисляет разницу и не уведомляет адаптер об изменении набора данных.

Ответ 7

с Kotlin вам просто нужно преобразовать свой список в новый MutableList, такой как этот или другой тип списка в соответствии с вашим использованием

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