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

คลาสที่แตกต่างของฉัน

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()เพราะอะแดปเตอร์รายการมียูทิลิตี้ที่แตกต่างกันในตัว

ใครสามารถช่วยฉันที่นี่?


ปัญหาอาจจะเกี่ยวข้องกับการArtistsDiffและทำให้การดำเนินงานของArtistตัวเอง
tynn

ใช่ฉันก็คิดเหมือนกัน แต่ดูเหมือนจะปักหมุดไม่ได้
Veeresh Charantimath

คุณสามารถดีบักหรือเพิ่มคำสั่งบันทึก นอกจากนี้คุณสามารถเพิ่มรหัสที่เกี่ยวข้องให้กับคำถาม
tynn

ตรวจสอบคำถามนี้ด้วยฉันแก้ไขมันแตกต่างกันstackoverflow.com/questions/58232606/…
MisterCat

คำตอบ:


107

แก้ไข: ฉันเข้าใจว่าเหตุใดสิ่งนี้จึงไม่ใช่ประเด็นของฉัน ประเด็นของฉันคืออย่างน้อยต้องให้คำเตือนหรือเรียกใช้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()ตอนนี้ LIfe ดีมาก เสียเวลาไปหลายชั่วโมง
Udayaditya Barua

1
@insa_c คุณสามารถเพิ่ม 3 ชั่วโมงในการนับของคุณนั่นเป็นจำนวนเงินที่ฉันเสียไปมากในการพยายามทำความเข้าใจว่าทำไม listview ของฉันถึงไม่อัปเดตในบางกรณีขอบ ...
Bencri

1
notifyDataSetChanged()มีราคาแพงและจะเอาชนะจุดที่มีการใช้งาน DiffUtil ได้อย่างสมบูรณ์ คุณอาจระมัดระวังและตั้งใจที่จะโทรsubmitListด้วยข้อมูลใหม่เท่านั้น แต่จริงๆแล้วนั่นเป็นเพียงกับดักประสิทธิภาพเท่านั้น
David Liu

63

ไลบรารีจะถือว่าคุณกำลังใช้ Room หรือ ORM อื่น ๆ ซึ่งเสนอรายการ async ใหม่ทุกครั้งที่มีการอัปเดตดังนั้นเพียงแค่เรียก submitList บนไฟล์ดังกล่าวก็จะใช้งานได้และสำหรับนักพัฒนาที่เลอะเทอะจะป้องกันไม่ให้ทำการคำนวณซ้ำสองครั้งหากมีการเรียกรายการเดียวกัน

คำตอบที่ยอมรับนั้นถูกต้องมีคำอธิบาย แต่ไม่ใช่คำตอบ

สิ่งที่คุณสามารถทำได้ในกรณีที่คุณไม่ได้ใช้ไลบรารีดังกล่าวคือ:

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

ตรรกะที่น่าสงสัย แต่ทำงานได้อย่างสมบูรณ์ วิธีที่ฉันชอบคือวิธีที่สองเนื่องจากไม่ทำให้แต่ละแถวได้รับการโทร onBind


7
นั่นเป็นการแฮ็ก เพียงแค่ส่งสำเนารายการ .submitList(new ArrayList(list))
Paul Woitaschek

3
ฉันใช้เวลาชั่วโมงสุดท้ายในการพยายามคิดว่าปัญหาคืออะไรกับตรรกะของฉัน ตรรกะแปลก ๆ
Jerry Okafor

7
@PaulWoitaschek นี่ไม่ใช่แฮ็ค แต่เป็นการใช้ JAVA :) ใช้เพื่อแก้ไขปัญหาต่างๆในไลบรารีที่นักพัฒนากำลัง "นอนหลับ" เหตุผลที่คุณเลือกสิ่งนี้แทนที่จะส่ง. SubmitList (ArrayList ใหม่ (รายการ)) เป็นเพราะคุณอาจส่งรายการในหลายที่ในรหัสของคุณ คุณอาจลืมสร้างอาร์เรย์ใหม่ทุกครั้งนั่นคือเหตุผลที่คุณลบล้าง
RJFares

2
แม้จะใช้ Room ฉันก็ประสบปัญหาที่คล้ายกัน
Bink

2
เห็นได้ชัดว่าสิ่งนี้ใช้งานได้เมื่ออัปเดตรายการใน viewmodel ด้วยรายการใหม่ แต่เมื่อฉันอัปเดตคุณสมบัติ (บูลีน - isSelected) ของรายการในรายการสิ่งนี้ยังคงใช้ไม่ได้ .. Idk ทำไม แต่ DiffUtil ส่งคืนรายการเก่าและใหม่เหมือนกันกับฉัน ' ตรวจสอบแล้ว ความคิดใดที่ปัญหาอาจเกิดขึ้น?
Ralph

23

ด้วย Kotlin เพียงแค่คุณต้องแปลงรายการของคุณเป็นMutableListใหม่เช่นนี้หรือรายการประเภทอื่นตามการใช้งานของคุณ

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

มันแปลก แต่การแปลงรายการเป็น mutableList ใช้ได้กับฉัน ขอบคุณ!
Thanh-Nhon Nguyen

4
ทำไมในนรกถึงใช้งานได้? ได้ผล แต่อยากรู้มากว่าทำไมถึงเกิดขึ้น
มีนาคม 3

ในความคิดของฉัน ListAdapter ต้องไม่เกี่ยวกับการอ้างอิงรายการของคุณดังนั้นด้วยใช่ไหม .toMutableList () คุณส่งรายการอินสแตนซ์ใหม่ไปยังอะแดปเตอร์ ฉันหวังว่าจะชัดเจนพอสำหรับคุณ @ March3April4
Mina Samir

ขอบคุณ. ตามความคิดเห็นของคุณฉันเดาว่า ListAdapter ได้รับชุดข้อมูลเป็นรูปแบบของ List <T> ซึ่งอาจเป็นรายการที่ไม่แน่นอนหรือแม้แต่รายการที่ไม่เปลี่ยนรูปก็ได้ หากฉันส่งรายการที่ไม่เปลี่ยนรูปออกการเปลี่ยนแปลงที่ฉันทำจะถูกบล็อกโดยชุดข้อมูลเองไม่ใช่โดย ListAdapter
มีนาคม 3

ฉันคิดว่าคุณเข้าใจแล้ว @ March3April4 นอกจากนี้โปรดดูแลเกี่ยวกับกลไกที่คุณใช้กับ diff utils ด้วยเพราะมันมีหน้าที่ในการคำนวณรายการในรายการว่าควรเปลี่ยนหรือไม่;)
Mina Samir

10

ฉันมีปัญหาที่คล้ายกัน แต่การแสดงผลที่ไม่ถูกต้องเกิดจากการรวมกันของsetHasFixedSize(true)และandroid:layout_height="wrap_content". 0สำหรับครั้งแรกอะแดปเตอร์มาพร้อมกับรายการที่ว่างเปล่าเพื่อให้ความสูงไม่เคยได้รับการปรับปรุงและเป็น อย่างไรก็ตามสิ่งนี้ช่วยแก้ปัญหาของฉันได้ คนอื่นอาจมีปัญหาเดียวกันและคิดว่าเป็นปัญหาในอะแดปเตอร์


1
ใช่ตั้งค่า recycleview เป็น wrap_content จะอัปเดตรายการหากคุณตั้งค่าเป็น match_parent มันจะไม่เรียกอะแดปเตอร์
Exel Staderlin

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 Arian Nejad

3

ตามเอกสารอย่างเป็นทางการ:

เมื่อใดก็ตามที่คุณเรียก submitListมันจะส่งรายการใหม่ที่แตกต่างและแสดง

นี่คือเหตุผลที่เมื่อใดก็ตามที่คุณเรียก submitListในรายการก่อนหน้านี้ (รายการที่ส่งไปแล้ว) จะไม่คำนวณความแตกต่างและไม่แจ้งให้อะแด็ปเตอร์ทราบถึงการเปลี่ยนแปลงในชุดข้อมูล


3

ในกรณีของฉันฉันลืมตั้งค่าLayoutManagerสำหรับไฟล์RecyclerView. ผลของสิ่งนั้นเหมือนกับที่อธิบายไว้ข้างต้น


2

สำหรับผมเรื่องนี้ปรากฏถ้าฉันถูกใช้RecyclerViewภายในScrollViewด้วยnestedScrollingEnabled="false"และการตั้งค่าความสูง RV wrap_contentไป
อะแด็ปเตอร์ได้รับการอัปเดตอย่างถูกต้องและมีการเรียกใช้ฟังก์ชันการผูก แต่รายการไม่ปรากฏขึ้น - RecyclerViewติดอยู่ที่ 'ขนาดดั้งเดิม'

การเปลี่ยนScrollViewเพื่อNestedScrollViewแก้ไขปัญหา


2

ฉันมีปัญหาที่คล้ายกัน ปัญหาอยู่ที่Diffฟังก์ชันซึ่งไม่ได้เปรียบเทียบรายการอย่างเพียงพอ ทุกคนที่มีปัญหานี้ตรวจสอบให้แน่ใจว่าDiffฟังก์ชันของคุณ(และโดยการขยายคลาสอ็อบเจ็กต์ข้อมูลของคุณ) มีข้อกำหนดการเปรียบเทียบที่เหมาะสมนั่นคือการเปรียบเทียบฟิลด์ทั้งหมดที่อาจได้รับการอัปเดตในรายการใหม่ ตัวอย่างเช่นในโพสต์ต้นฉบับ

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

ฟังก์ชั่นนี้ (อาจ) ไม่ทำตามที่ระบุไว้บนฉลาก: จะไม่เปรียบเทียบเนื้อหาของทั้งสองรายการ - เว้นแต่คุณจะได้ลบล้างequals()ฟังก์ชันในArtistคลาส ในกรณีของฉันฉันไม่ได้และคำจำกัดความของareContentsTheSameการตรวจสอบเพียงหนึ่งในฟิลด์ที่จำเป็นเนื่องจากการกำกับดูแลของฉันเมื่อนำไปใช้ นี่คือความเสมอภาคเชิงโครงสร้างเทียบกับความเท่าเทียมกันของการอ้างอิงคุณสามารถค้นหาเพิ่มเติมได้ที่นี่


1

สำหรับใครก็ตามที่สถานการณ์เหมือนกับของฉันฉันออกจากวิธีแก้ปัญหาของฉันซึ่งฉันไม่รู้ว่าทำไมมันถึงได้ผลที่นี่

วิธีแก้ปัญหาที่ได้ผลสำหรับฉันมาจาก @Mina Samir ซึ่งส่งรายชื่อเป็นรายการที่ไม่แน่นอน

สถานการณ์ปัญหาของฉัน:

- กำลังโหลดรายชื่อเพื่อนภายในส่วน

  1. ActivityMain แนบ FragmentFriendList (สังเกตจากข้อมูลที่มีชีวิตของไอเท็มฐานข้อมูลเพื่อน) และในเวลาเดียวกันขอ http ไปยังเซิร์ฟเวอร์เพื่อรับรายชื่อเพื่อนทั้งหมดของฉัน

  2. อัปเดตหรือแทรกรายการจากเซิร์ฟเวอร์ http

  3. การเปลี่ยนแปลงทุกครั้งจะจุดชนวนการเรียกกลับ onChanged ของ liveata แต่เมื่อเป็นครั้งแรกที่ฉันเปิดแอปพลิเคชันซึ่งหมายความว่าไม่มีสิ่งใดอยู่บนโต๊ะของฉัน submitList ประสบความสำเร็จโดยไม่มีข้อผิดพลาดใด ๆ แต่ไม่มีอะไรปรากฏบนหน้าจอ

  4. อย่างไรก็ตามเมื่อฉันเปิดแอปพลิเคชันเป็นครั้งที่สองข้อมูลจะถูกโหลดไปที่หน้าจอ

วิธีแก้ปัญหาคือตามที่ metioned ข้างต้นส่งรายการเป็น 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.