ListAdapter không cập nhật mục trong RecyclerView


89

Tôi đang sử dụng thư viện hỗ trợ mới ListAdapter. Đây là mã của tôi cho bộ điều hợp

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

Tôi đang cập nhật bộ điều hợp với

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

Lớp khác của tôi

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

Điều gì đang xảy ra là khi submitList được gọi lần đầu tiên bộ điều hợp hiển thị tất cả các mục, nhưng khi submitList được gọi lại với các thuộc tính đối tượng được cập nhật, nó không hiển thị lại chế độ xem đã thay đổi.

Nó hiển thị lại chế độ xem khi tôi cuộn danh sách, đến lượt nó gọi bindView()

Ngoài ra, tôi nhận thấy rằng việc gọi adapter.notifyDatasSetChanged()sau khi gửi danh sách hiển thị chế độ xem với các giá trị cập nhật, nhưng tôi không muốn gọi notifyDataSetChanged()vì bộ điều hợp danh sách có tích hợp sẵn diff utils

bất cứ ai có thể giúp tôi ở đây?


Vấn đề có thể liên quan đến ArtistsDiffvà do đó đến việc thực hiện Artistchính nó.
tynn

Vâng, tôi quá suy nghĩ giống nhau, nhưng tôi dường như không thể điểm pin nó
Veeresh Charantimath

Bạn có thể gỡ lỗi nó hoặc thêm các câu lệnh nhật ký. Ngoài ra, bạn có thể thêm mã có liên quan vào câu hỏi.
tynn

cũng kiểm tra câu hỏi này, tôi đã giải quyết nó theo cách khác stackoverflow.com/questions/58232606/…
MisterCat

Câu trả lời:


100

Chỉnh sửa: Tôi hiểu tại sao điều này xảy ra mà không phải là quan điểm của tôi. Quan điểm của tôi là ít nhất nó cần phải đưa ra một cảnh báo hoặc gọinotifyDataSetChanged() hàm. Bởi vì rõ ràng tôi đang gọi submitList(...)hàm là có lý do. Tôi khá chắc chắn rằng mọi người đang cố gắng tìm ra điều gì đã xảy ra trong nhiều giờ cho đến khi họ phát hiện ra submitList () bỏ qua một cách im lặng lệnh gọi.

Điều này là do Googlelogic kỳ lạ. Vì vậy, nếu bạn chuyển cùng một danh sách cho bộ điều hợp, nó thậm chí không gọi DiffUtil.

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

Tôi thực sự không hiểu toàn bộ vấn đề này ListAdapternếu nó không thể xử lý các thay đổi trên cùng một danh sách. Nếu bạn muốn thay đổi các mục trong danh sách mà bạn chuyển đến ListAdaptervà xem các thay đổi thì bạn cần tạo một bản sao sâu của danh sách hoặc bạn cần sử dụng thông thường RecyclerViewvới DiffUtilllớp của riêng mình .


5
Bởi vì nó yêu cầu trạng thái trước đó để thực hiện khác biệt. Tất nhiên nó không thể xử lý nếu bạn ghi đè lên trạng thái trước đó. O_o
EpicPandaForce

30
Có nhưng tại thời điểm đó, có một lý do tại sao tôi gọi là submitList, phải không? Nó ít nhất nên gọi notifyDataSetChanged()thay vì im lặng bỏ qua cuộc gọi. Tôi khá chắc chắn rằng mọi người đang cố gắng tìm ra điều gì đã xảy ra trong nhiều giờ cho đến khi họ phát hiện ra submitList()cuộc gọi im lặng bỏ qua.
insa_c

6
Vì vậy, tôi trở lại RecyclerView.Adapter<VH>notifyDataSetChanged(). LIfe là tốt bây giờ. Lãng phí số giờ tốt
Udayaditya Barua

@insa_c Bạn có thể thêm 3 giờ vào số lượng của mình, đó là số tiền tôi đã lãng phí khi cố gắng hiểu tại sao chế độ xem danh sách của tôi không cập nhật trong một số trường hợp ...
Bencri

1
notifyDataSetChanged()đắt tiền và sẽ đánh bại hoàn toàn quan điểm về việc triển khai dựa trên DiffUtil. Bạn có thể cẩn thận và submitListcó ý định chỉ gọi với dữ liệu mới, nhưng thực sự đó chỉ là một cái bẫy hiệu suất.
David Liu

61

Thư viện giả định rằng bạn đang sử dụng Room hoặc bất kỳ ORM nào khác cung cấp danh sách không đồng bộ mới mỗi khi được cập nhật, vì vậy chỉ cần gọi submitList trên đó sẽ hoạt động và đối với các nhà phát triển cẩu thả, nó ngăn không cho thực hiện tính toán hai lần nếu cùng một danh sách được gọi.

Câu trả lời được chấp nhận là đúng, nó đưa ra lời giải thích nhưng không phải là giải pháp.

Những gì bạn có thể làm trong trường hợp bạn không sử dụng bất kỳ thư viện nào như vậy là:

submitList(null);
submitList(myList);

Một giải pháp khác là ghi đè danh sách submitList (không gây ra hiện tượng nhấp nháy nhanh) như sau:

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

Hoặc với mã Kotlin:

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

Logic đáng nghi vấn nhưng hoạt động hoàn hảo. Phương pháp ưa thích của tôi là phương pháp thứ hai vì nó không khiến mỗi hàng nhận được lệnh gọi onBind.


4
Đó là một vụ hack. Chỉ cần chuyển một bản sao của danh sách. .submitList(new ArrayList(list))
Paul Woitaschek

2
Tôi đã dành một giờ qua để tìm ra vấn đề với logic của mình. Thật là một logic kỳ lạ.
Jerry Okafor

7
@PaulWoitaschek Đây không phải là hack, đây là sử dụng JAVA :) Nó được sử dụng để khắc phục nhiều sự cố trong các thư viện nơi nhà phát triển đang "ngủ". Lý do tại sao bạn chọn điều này thay vì chuyển .submitList (ArrayList mới (danh sách)) là vì bạn có thể gửi danh sách ở nhiều vị trí trong mã của mình. Bạn có thể quên tạo một mảng mới mỗi lần, đó là lý do tại sao bạn ghi đè.
RJFares

1
@ Po10cio Thật kỳ lạ chủ yếu là vì khi họ viết nó như vậy, người ta cho rằng nó sẽ chỉ được sử dụng với các thư viện ORM luôn cung cấp danh sách mới. Nếu bạn đang vượt qua cùng một danh sách nhưng được cập nhật, bạn phải giải quyết vấn đề đó và đó sẽ là cách tốt nhất
RJFares

1
Ngay cả khi sử dụng Room, tôi cũng gặp phải vấn đề tương tự.
Bink

21

với Kotlin, bạn chỉ cần chuyển đổi danh sách của mình thành MutableList mới như thế này hoặc một loại danh sách khác tùy theo cách sử dụng của bạn

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

Điều đó thật kỳ lạ nhưng việc chuyển đổi danh sách thành mutableList phù hợp với tôi. Cảm ơn!
Thanh-Nhon Nguyen

3
Tại sao cái này lại hoạt động? Nó hoạt động nhưng rất tò mò tại sao điều này xảy ra.
March3April4

theo ý kiến ​​của tôi, ListAdapter không được liên quan đến tham chiếu danh sách của bạn, do đó? .toMutableList () bạn gửi một danh sách phiên bản mới cho bộ điều hợp. Tôi hy vọng rằng đủ rõ ràng cho bạn. @
Mina Samir

Cảm ơn. Theo nhận xét của bạn, tôi đoán rằng ListAdapter nhận được tập dữ liệu của nó dưới dạng một dạng Danh sách <T>, có thể là một danh sách có thể thay đổi hoặc thậm chí là một danh sách bất biến. Nếu tôi chuyển ra một danh sách không thể thay đổi, những thay đổi tôi đã thực hiện đang bị chính tập dữ liệu chặn, không phải bởi ListAdapter.
March3April4

Tôi nghĩ bạn hiểu rồi @ March3April4 Ngoài ra, hãy quan tâm đến cơ chế bạn sử dụng với diff utils vì nó cũng có trách nhiệm sẽ tính toán các mục trong danh sách có nên thay đổi hay không;)
Mina Samir

10

Tôi đã gặp sự cố tương tự nhưng kết xuất không chính xác là do sự kết hợp của setHasFixedSize(true)android:layout_height="wrap_content". Lần đầu tiên bộ điều hợp được cung cấp với một danh sách trống nên chiều cao không bao giờ được cập nhật và được 0. Dù sao, điều này đã ảnh hưởng đến vấn đề của tôi. Người khác có thể gặp vấn đề tương tự và sẽ nghĩ rằng đó là vấn đề trong bộ điều hợp.


1
Vâng, đặt chế độ xem lại thành wrap_content sẽ cập nhật danh sách, nếu bạn đặt nó thành match_parent nó sẽ không gọi bộ điều hợp
Exel Staderlin

5

Nếu bạn gặp một số vấn đề khi sử dụng

recycler_view.setHasFixedSize(true)

bạn nên chắc chắn kiểm tra nhận xét này: https://github.com/thoughtbot/expandable-recycler-view/issues/53#issuecomment-362991531

Nó đã giải quyết vấn đề về phía tôi.

(Đây là ảnh chụp màn hình của nhận xét theo yêu cầu)

nhập mô tả hình ảnh ở đây


Liên kết đến một giải pháp được hoan nghênh, nhưng hãy đảm bảo rằng câu trả lời của bạn hữu ích nếu không có nó: thêm ngữ cảnh xung quanh liên kết để những người dùng đồng nghiệp của bạn sẽ biết nó là gì và tại sao nó ở đó, sau đó trích dẫn phần có liên quan nhất của trang bạn ' đang liên kết lại trong trường hợp trang đích không có sẵn.
Mostafa Arian Nejad

4

Hôm nay tôi cũng tình cờ gặp "sự cố" này. Với sự trợ giúp của câu trả lời của insa_cgiải pháp của RJFares, tôi đã tạo cho mình một hàm mở rộng 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)
}

sau đó có thể được sử dụng ở bất cứ đâu, ví dụ:

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

và nó chỉ (và luôn) sao chép danh sách nếu nó giống với danh sách hiện đang được tải.


3

Theo tài liệu chính thức :

Bất cứ khi nào bạn gọi submitList, nó sẽ gửi một danh sách mới để thay đổi và hiển thị.

Đây là lý do tại sao bất cứ khi nào bạn gọi submitList trên danh sách trước đó (đã được gửi), nó không tính toán Chênh lệchkhông thông báo cho bộ điều hợp về sự thay đổi trong tập dữ liệu.


2

Đối với tôi, vấn đề này xuất hiện nếu tôi đang sử dụng chiều cao RecyclerViewbên trong của ScrollViewwith nestedScrollingEnabled="false"và RV được đặt thành wrap_content.
Bộ điều hợp được cập nhật đúng cách và chức năng liên kết được gọi, nhưng các mục không được hiển thị - bộ điều hợp RecyclerViewbị kẹt ở 'kích thước ban đầu của nó.

Thay đổi ScrollViewđể NestedScrollViewkhắc phục sự cố.


2

Trong trường hợp của tôi, tôi đã quên đặt LayoutManagercho RecyclerView. Hiệu quả của điều đó giống như mô tả ở trên.


1

Đối với bất kỳ ai có kịch bản giống như của tôi, tôi để lại giải pháp của mình, mà tôi không biết tại sao nó hoạt động, tại đây.

Giải pháp phù hợp với tôi là từ @Mina Samir, đang gửi danh sách dưới dạng danh sách có thể thay đổi.

Tình huống sự cố của tôi:

-Lưu trữ danh sách bạn bè bên trong một phân mảnh.

  1. ActivityMain đính kèm FragmentFriendList (Quan sát dữ liệu sống của các mục db của bạn bè) và đồng thời, gửi yêu cầu http đến máy chủ để lấy tất cả danh sách bạn bè của tôi.

  2. Cập nhật hoặc chèn các mục từ máy chủ http.

  3. Mỗi thay đổi sẽ kích hoạt lệnh gọi lại onChanged của dữ liệu sống. Tuy nhiên, khi đây là lần đầu tiên tôi khởi chạy ứng dụng, có nghĩa là không có gì trên bàn của tôi, submitList thành công mà không có bất kỳ lỗi nào dưới bất kỳ hình thức nào, nhưng không có gì xuất hiện trên màn hình.

  4. Tuy nhiên, khi đây là lần thứ hai tôi khởi chạy ứng dụng, dữ liệu đang được tải lên màn hình.

Giải pháp là, như đã đề cập ở trên, gửi danh sách dưới dạng danh sách có thể thay đổi.


1

Tôi đã có một vấn đề tương tự. Vấn đề là ở các Diffchức năng, không so sánh đầy đủ các mục. Bất kỳ ai gặp sự cố này, hãy đảm bảo rằng các Diffhàm của bạn (và bằng cách mở rộng các lớp đối tượng dữ liệu của bạn) chứa các định nghĩa so sánh phù hợp - tức là so sánh tất cả các trường có thể được cập nhật trong mục mới. Ví dụ trong bài gốc

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

Hàm này (có thể) không thực hiện những gì nó ghi trên nhãn: nó không so sánh nội dung của hai mục - trừ khi bạn đã ghi đè equals()hàm trong Artistlớp. Trong trường hợp của tôi, tôi đã không, và định nghĩa areContentsTheSamechỉ kiểm tra một trong các trường cần thiết, do sự giám sát của tôi khi thực hiện nó. Đây là bình đẳng cấu trúc so với bình đẳng tham chiếu, bạn có thể tìm thêm về nó tại đây


0

Tôi cần sửa đổi DiffUtils của mình

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

Để thực sự trả về nội dung có mới hay không, không chỉ so sánh id của mô hình.


0

Sử dụng câu trả lời đầu tiên @RJFares cập nhật danh sách thành công, nhưng không duy trì trạng thái cuộn. Toàn bộ RecyclerViewbắt đầu từ vị trí thứ 0. Để giải quyết vấn đề, đây là những gì tôi đã làm:

   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

   }

Bằng cách này, tôi có thể duy trì trạng thái cuộn RecyclerViewcùng với dữ liệu đã sửa đổi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.