RecyclerView: Inkonsistensi terdeteksi. Posisi item tidak valid

271

QA kami telah mendeteksi bug: ketika memutar perangkat Android (Droid Turbo), kecelakaan terkait RecyclerView berikut terjadi:

java.lang.IndexOutOfBoundsException: Ketidakkonsistenan terdeteksi. Posisi item 2 tidak valid (offset: 2) .state: 3

Bagi saya, sepertinya ada kesalahan internal di dalam RecyclerView, karena saya tidak bisa memikirkan cara apa pun yang disebabkan langsung oleh kode kami ...

Pernahkah seseorang menjumpai masalah ini?

Apa solusinya?

Solusi brutal mungkin bisa untuk menangkap pengecualian ketika itu terjadi dan membuat kembali instance RecyclverView dari awal, untuk menghindari dibiarkan dengan keadaan rusak.

Tetapi, jika memungkinkan, saya ingin memahami masalahnya dengan lebih baik (dan mungkin memperbaikinya pada sumbernya), daripada menutupinya.

Bugnya tidak mudah direproduksi, tetapi fatal saat terjadi.

Jejak tumpukan penuh:

W/dalvikvm( 7546): threadid=1: thread exiting with uncaught exception (group=0x41987d40)
    E/AndroidRuntime( 7546): FATAL EXCEPTION: main
    E/AndroidRuntime( 7546): Process: com.oblong.mezzedroid, PID: 7546
    E/AndroidRuntime( 7546): java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:3
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3382)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3340)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1810)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1306)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1269)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:523)
    E/AndroidRuntime( 7546):    at org.liboid.recycler_view.RecyclerViewContainer$LiLinearLayoutManager.onLayoutChildren(RecyclerViewContainer.java:179)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1942)
    E/AndroidRuntime( 7546):    at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2237)
    E/AndroidRuntime( 7546):    at org.liboid.recycler_view.LiRecyclerView.onLayout(LiRecyclerView.java:30)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at com.oblong.mezzedroid.workspace.content.bins.BinsContainerLayout.onLayout(BinsContainerLayout.java:22)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    E/AndroidRuntime( 7546):    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    E/AndroidRuntime( 7546):    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    E/AndroidRuntime( 7546):    at android.view.View.layout(View.java:14946)
    E/AndroidRuntime( 7546):    at android.view.ViewGroup.layout(ViewGroup.java:4651)
    E/AndroidRuntime( 7546):    at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2132)
    E/AndroidRuntime( 7546):    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1872)
    E/AndroidRuntime( 7546):    at andro
KarolDepka
sumber
2
Sebuah pertanyaan: Seberapa konsisten repro Anda? Saya tahu ini adalah kesalahan dalam kode google di sini dan di sini . Tapi ini bisa dihindari. Jadi, apakah ini terjadi pada setiap rotasi?
VicVu
Hai. Ini jarang terjadi, tetapi ketika itu terjadi, itu fatal bagi aplikasi.
KarolDepka
Terima kasih atas tautannya ke bug. Yang pertama tampaknya lebih relevan daripada yang kedua.
KarolDepka
1
Ya saya pikir taruhan terbaik Anda adalah tidak mengizinkan perubahan pada tampilan daftar selama rotasi.
VicVu
1
Jika Anda dapat mereproduksi dengan mudah, saya sarankan mencetak nilai untuk 'getItemCount' sebelum semua panggilan untuk 'beri tahu *' ... Anda mungkin menemukan bahwa jumlah item Anda tidak sesuai dengan asumsi Anda.
Rich Ehmer

Jawaban:

209

Saya memiliki (mungkin) masalah terkait - memasuki contoh baru dari suatu kegiatan dengan RecyclerView, tetapi dengan adaptor yang lebih kecil memicu kerusakan ini untuk saya.

RecyclerView.dispatchLayout()dapat mencoba menarik item dari memo sebelum menelepon mRecycler.clearOldPositions(). Konsekuensinya adalah bahwa itu menarik item dari kolam umum yang memiliki posisi lebih tinggi dari ukuran adaptor.

Untungnya, ini hanya dilakukan jika PredictiveAnimationsdiaktifkan, jadi solusi saya adalah dengan subkelas GridLayoutManager( LinearLayoutManagermemiliki masalah dan 'perbaikan' yang sama), dan mengganti supportsPredictiveItemAnimations()untuk mengembalikan false:

/**
 * No Predictive Animations GridLayoutManager
 */
private static class NpaGridLayoutManager extends GridLayoutManager {
    /**
     * Disable predictive animations. There is a bug in RecyclerView which causes views that
     * are being reloaded to pull invalid ViewHolders from the internal recycler stack if the
     * adapter size has decreased since the ViewHolder was recycled.
     */
    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }

    public NpaGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public NpaGridLayoutManager(Context context, int spanCount) {
        super(context, spanCount);
    }

    public NpaGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }
}
Kas Hunt
sumber
4
Ini berfungsi untuk saya, dan menonaktifkan animasi prediktif tidak menyebabkan Anda kehilangan animasi secara bersamaan. Bravo.
Robert Liberatore 3-15
8
Terima kasih banyak Pak! Bekerja instan dengan LinearLayoutManager, mungkin menyelamatkan saya berhari-hari.
levavare
8
Terima kasih banyak. Solusi ini bekerja dengan LinearLayoutManager.
Pruthviraj
8
Saya pikir orang ini pantas kita membangun sebuah patung untuk menghormati bantuannya yang berharga ... Ini adalah salah satu masalah terburuk yang didokumentasikan di web, tetapi tampaknya banyak pengembang yang mengalami masalah ini ... Saya hanya ingin tahu bagaimana Anda dapat menemukannya dilewati jika PredictiveAnimations salah, @KasHunt? Karena, stacktrace sangat tidak jelas ...
PAD
4
Adakah yang tahu, bagaimana cara memperbaikinya tanpa retasan ini? Karena notifyDatasetChanged dibatalkan demi DiffUtil
Anton Shkurenko
83

Dalam kasus saya (hapus / masukkan data dalam struktur data saya), saya perlu menghapus kumpulan daur ulang dan kemudian memberi tahu kumpulan data berubah!

mRecyclerView.getRecycledViewPool().clear(); mAdapter.notifyDataSetChanged();

MatejC
sumber
6
Saya biasanya tidak mengatakan ini, TETAPI TERIMA KASIH BANYAK. Saya telah mencoba SEGALA SESUATU untuk memperbaiki kerusakan ini yang terjadi secara sporadis ketika saya memindahkan banyak item dalam daftar dengan cepat. Saya telah menghabiskan mungkin seminggu penuh untuk mencoba memecahkan masalah ini. Saya berjalan menjauh darinya selama beberapa bulan untuk mencoba memberi otak saya kesempatan untuk mendekatinya secara berbeda, kemudian menemukan ini pada upaya Google pertama saya. Diberkatilah Anda!
Chantell Osejo
Biarkan saya memberi Anda satu ton cookie karena Anda layak mendapatkannya. Terima kasih.
antonis_st
mengapa kamu harus melakukan ini?
dabluck
9
Itu operasi yang cukup berat yang mengalahkan tujuan daur ulang tampilan.
gjsalot
@ gjsalot Jadi jika saya menggunakan ini, dapatkah itu menyebabkan beberapa masalah?
Sreekanth Karumanaghat
38

Gunakan notifyDataSetChanged()sebagai gantinya notifyItem...dalam hal ini.

khaintt
sumber
5
Dalam beberapa kasus ini adalah jalan yang harus ditempuh. Saya mempunyai situasi di mana saya mengganti semua barang saya, tetapi saya tidak jujur ​​dengan adaptor, hanya mengatakan bahwa saya telah memasukkan beberapa barang baru (notifyItemRangeInserted), tanpa terlebih dahulu memberi tahu saya juga telah menghapus barang. Adaptor kemudian berharap akan ada lebih banyak item daripada yang sebenarnya ada. Jika menggunakan salah satu ini adaptor memberitahukan metode mengharapkan notifyDataSetChanged, seperti notifyItemRangeRemoved / Disisipkan / Diperbarui, pemanggil memiliki tanggung jawab penuh menceritakan adaptor persis apa yang berubah, atau Anda mungkin berakhir dengan ini "keadaan tidak konsisten".
JHH
19
Ini bukan solusi sama sekali.
Miha_x64
Ini bukan jalan yang harus ditempuh. Jika ini berfungsi, itu berarti Anda hanya mengacaukan kisaran notifyItem...dan mengoreksi yang akan mulai berfungsi alih-alih merender ulang semua item.
Ranjan
12

Saya memecahkan ini dengan menunda mRecycler.setAdapter(itemsAdapter)sampai setelah menambahkan semua item ke adaptor dengan mRecycler.addAll(items)dan itu berhasil. Tidak tahu mengapa saya melakukan itu sejak awal, itu dari kode perpustakaan yang saya melihat dan melihat garis-garis itu dalam "urutan yang salah", saya cukup yakin ini dia, tolong jika seseorang dapat mengkonfirmasi menjelaskan mengapa itu begitu? Tidak yakin apakah ini jawaban yang valid bahkan

Odaym
sumber
Saya pikir ini solusinya, setelah saya menunda adaptor itu baik-baik saja saya percaya ... sekarang muncul ketika mengatur adaptor di utas UI dan menambahkan item ke dalamnya.
EngineSense
18
Saya menggunakan swapAdapter(adapter, true)bukan setAdapter(adapter)dan itu membantu.
frangulyan
11

Saya memiliki masalah yang sama tetapi tidak persis sama. Dalam kasus saya pada 1 titik saya sedang membersihkan array yang diteruskan ke recyclerview

mObjects.clear();

dan tidak memanggil notifyDataSetChanged, karena saya tidak ingin recyclerview segera menghapus pandangan. Saya mengisi ulang array mObjects di AsyncTask.

Aalap
sumber
9

Saya memiliki masalah yang sama dengan recyclerView. Jadi saya hanya memberi tahu adaptor tentang perubahan set data setelah daftar dihapus.

mList.clear();
mAdapter.notifyDataSetChanged();

mList.addAll(newData);
mAdapter.notifyDataSetChanged();
Reza
sumber
1
Kesalahan sederhana ini membuat saya kehilangan banyak waktu, terima kasih banyak!
leb1755
7

Saya memiliki masalah yang sama. Itu terjadi ketika saya menggulir cepat dan memanggil API dan memperbarui data. Setelah mencoba semua hal untuk mencegah kerusakan, saya menemukan solusi.

mRecyclerView.stopScroll();

Itu akan berhasil.

Anand Savjani
sumber
Ini adalah solusi bukan perbaikan. Anda memaksanya untuk menghentikan gulir. Bad UX
Dr. aNdRO
1
@ Dr.aNdRO: Adaptor perlu mengatur posisi dan jika Anda terus menggulir tinjauan ulang, adaptor tidak dapat mengatur data yang merupakan penyebab crash. Ini bukan UX yang buruk
Anand Savjani
1
masuk akal. Menghentikan gulir tidak buruk karena refresh data terjadi.
Sush
6

Saya mengubah data untuk RecyclerViewlatar belakang Thread. Saya mendapat hal yang sama Exceptiondengan OP. Saya menambahkan ini setelah mengubah data:

myRecyclerView.post(new Runnable() {
    @Override
    public void run() {
        myRecyclerAdapter.notifyDataSetChanged();
    }
});

Semoga ini bisa membantu

kashyap jimuliya
sumber
Terima kasih sobat! ini adalah satu-satunya jawaban yang masuk akal dari sudut pandang pengembangan Android.
user347187
Sementara saya juga menyelesaikannya dengan bantuan view.recycler_view.post, saya menggunakan notifyItemInserted. Dalam kasus saya ini sudah menjadi utas UI.
CoolMind
6

Kesalahan ini terjadi ketika daftar dalam adaptor dihapus ketika pengguna bergulir yang membuat posisi pemegang item berubah, kehilangan referensi antara daftar dan item pada ui, kesalahan terjadi pada permintaan "notDataSetChanged" berikutnya .

Memperbaiki:

Tinjau metode daftar pembaruan Anda. Jika Anda melakukan sesuatu seperti

mainList.clear();
...
mainList.add() or mainList.addAll()
...
notifyDataSetChanged();

===> Error occur

Bagaimana cara memperbaiki. Buat objek daftar baru untuk pemrosesan buffer dan tetapkan kembali ke daftar utama setelah itu

List res = new ArrayList();
…..
res.add();  //add item or modify list
….
mainList = res;
notifyDataSetChanged();

Terima kasih kepada Nhan Cao untuk bantuan yang luar biasa ini :)

Ramesh Pokharel
sumber
4

Masalah saya hilang setelah saya memodifikasi Adapterimplementasi saya untuk menggunakan salinan array item alih-alih referensi. The setItems()metode ini disebut setiap kali kita memiliki item baru untuk menunjukkan di RecyclerView.

Dari pada:

private class MyAdapter extends RecyclerView.Adapter<ItemHolder> {
     private List<MyItem> mItems;  

    (....)

    void setItems(List<MyItem> items) {
        mItems = items;
    }
}

Aku melakukannya:

void setItems(List<MyItem> items) {
    mItems = new ArrayList<>(items);
}
Miguel A. Gabriel
sumber
Ini akan menyelesaikan masalah, tetapi tidak akan memakan dua kali memori asli?
Sreekanth Karumanaghat
@ MiguelA.Gabriel ini akan mempengaruhi kinerja? misalnya dalam kasus saya, saya memperbarui array recylerview terlalu sering sehingga saat ini saya melakukan ini suggestionsRecyclerView.swapAdapter(new CandidatesAdapter(mSuggestions), true); dan ini adalah konstruktor saya public CandidatesAdapter(List<String> suggestionsList) { this.suggestionsList = new ArrayList<>(suggestionsList); }
Mateen Chaudhry
@ mateen-chaudhry Mungkin akan. Anda harus mengujinya dalam kasus Anda dan memutuskan, atau mencoba menggunakan solusi lain yang diusulkan. Seperti yang saya katakan, itu hanya solusi dan itu bekerja untuk saya dalam kasus saya.
Miguel A. Gabriel
3

Saya telah menghadapi situasi yang sama. Dan itu diselesaikan dengan menambahkan kode sebelum Anda menghapus koleksi Anda.

mRecyclerView.getRecycledViewPool().clear();

MagicDroidX
sumber
3

Dalam kasus saya, saya memperbarui item dan memanggil notifyDataSetChangedutas non-UI. Sebagian besar waktu itu berhasil, tetapi ketika banyak perubahan terjadi dengan cepat, itu akan crash. Sebaliknya, ketika saya melakukannya, pada dasarnya

activity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        changeData();
        notifyDataSetChanged();
    }
});

lalu berhenti menabrak.

Erhannis
sumber
3

Anda hanya perlu menghapus daftar Anda OnPostExecute()dan tidak saat melakukannyaPull to Refresh

// Setup refresh listener which triggers new data loading
        swipeContainer.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {

                AsyncTask<String,Void,String> task = new get_listings();
                task.execute(); // clear listing inside onPostExecute

            }
        });

Saya menemukan bahwa ini terjadi ketika Anda menggulir selama tarikan untuk menyegarkan , karena saya membersihkan daftar sebelum async task, hasilnya java.lang.IndexOutOfBoundsException: Inconsistency detected.

        swipeContainer.setRefreshing(false);
        //TODO : This is very crucial , You need to clear before populating new items 
        listings.clear();

Dengan begitu Anda tidak akan berakhir dengan ketidakkonsistenan

Fahad
sumber
2

Hal ini juga dapat dikaitkan dengan pengaturan adaptor beberapa kali secara bersamaan. Saya memiliki metode panggilan balik yang dipicu 5-6 kali pada saat yang sama dan saya sedang mengatur adaptor dalam panggilan balik itu sehingga RecycledViewPool tidak dapat menangani semua data tersebut secara bersamaan. Ini peluang yang besar, tetapi Anda sebaiknya memeriksanya.

Mustafa Güven
sumber
1
ya masalah yang sama .. Tapi solusinya? Anda memberi alasan saja .. Bagaimana cara memperbaikinya?
Ranjith Kumar
@RanjithKumar, Mohon bagikan cara Anda untuk mengatasi masalah di atas. Saya mengatasinya menggunakan mRecyclerView.getRecycledViewPool (). Clear (); sebelum notifyDataSetChanged dan menggunakan blok tersinkronkan seputar fungsi pembaruan adaptor
Attiq ur Rehman
bisakah Anda melihat kode saya, saya pikir masalah saya adalah seperti milik Anda, bisakah Anda membantu saya stackoverflow.com/questions/50213362/…
Mateen Chaudhry
2

Menggunakan

notifyDataSetChanged()

sebagai gantinya

notifyItemRangeInserted(0, YourArrayList.size())

pada kasus ini.

Pankaj Talaviya
sumber
1
tapi ini tidak baik untuk kinerja bukan? notifyItemRangeInserted lebih baik, masalahnya tidak terletak di sini
Derekyy
2

Untuk memperbaiki masalah ini, panggil saja notifyDataSetChanged () dengan daftar kosong sebelum memperbarui tampilan daur ulang.

Sebagai contoh

//Method for refresh recycle view

    if (!hcpArray.isEmpty())

hcpArray.clear (); // Daftar untuk pembaruan tampilan daur ulang

adapter.notifyDataSetChanged();
EKN
sumber
2
Bukan solusi.
Miha_x64
@Milha Saya tidak mendapatkan solusi lain untuk memperbaiki masalah mogok. Tetapi solusi di atas berhasil bagi saya. Jika itu bukan solusi, beri tahu saya perbaikan yang tepat.
EKN
Tergantung. Anda dapat mencoba menggunakan DiffUtil - alat tujuan umum untuk memperbarui konten RecyclerView.
Miha_x64
2

Dalam kasus saya, saya baru saja menghapus baris dengan setHasStableIds(true);

darkchaos
sumber
Tapi HasStableIds (benar) meningkatkan kinerja Rv, apakah ada solusi alternatif?
Sreekanth Karumanaghat
Sebenarnya saya pikir itu bisa karena alasan yang berbeda, jadi mungkin ada solusi yang berbeda untuk masalah ini berdasarkan apa yang menjadi akar penyebabnya.
Sreekanth Karumanaghat
2

Dalam kasus saya, saya mencoba untuk mengubah konten adaptor saya pada utas latar tetapi disebut notify * pada utas utama / ui.

Itu tidak mungkin! Alasan mengapa pemberitahuan dipaksa ke utas utama adalah bahwa recyclerview ingin Anda mengedit adaptor pendukung pada utas utama, bahkan pada tumpukan panggilan yang sama.

Untuk menyelesaikan masalah, pastikan setiap operasi ke adaptor Anda dan juga setiap pemberitahuan ... panggilan dilakukan di utas ui / utama !

Johannes Muenichsdorfer
sumber
2
menambahkan item ke daftar di adaptor Anda harus dilakukan di utas latar belakang dan panggil pemberitahuan di postexecute. menambahkan data dalam utas ui membuat aplikasi membeku selama beberapa milidetik atau detik jika menambahkan banyak data
dione llorera
setuju dengan @dionellorera, harus diperjelas bahwa "perubahan isi adaptor" secara spesifik berarti memodifikasi data secara langsung, apakah nilai primitif, properti objek, atau objek itu sendiri
OzzyTheGiant
2

Saya berlari ke jejak tumpukan jahat ini dengan Komponen Arsitektur Android baru-baru ini. Pada dasarnya, saya memiliki daftar item di ViewModel saya yang diamati oleh Fragmen saya, menggunakan LiveData. Ketika ViewModel memposting nilai baru untuk data, Fragment memperbarui adaptor, meneruskan elemen data baru ini dan memberi tahu adaptor bahwa ada perubahan.

Sayangnya, ketika meneruskan elemen data baru ke adaptor, saya gagal menjelaskan fakta bahwa ViewModel dan Adapter akan menunjuk ke referensi objek yang sama! Artinya, jika saya memperbarui data dan menelepon postValue()dari dalam ViewModel, ada jendela yang sangat kecil di mana data dapat diperbarui dan adaptor belum diberitahukan!

Perbaikan saya adalah untuk membuat salinan elemen baru ketika diteruskan ke adaptor:

mList = new ArrayList<>(passedList);

Dengan perbaikan super mudah ini Anda dapat memastikan bahwa data adaptor Anda tidak akan berubah hingga tepat sebelum adaptor Anda diberi tahu.

Steve
sumber
2

Ini hanya solusi yang bekerja untuk saya bahkan mencoba banyak dari solusi di atas.

1.) Intilisasi

CustomAdapter scrollStockAdapter = new CustomAdapter(mActivity, new ArrayList<StockListModel>());
list.setAdapter(scrollStockAdapter);
scrollStockAdapter.updateList(stockListModels);

2.) Tulis metode ini dalam adaptor

public void updateList(List<StockListModel> list) {
stockListModels.clear();
stockListModels.addAll(list);
notifyDataSetChanged();
}

stockListModels -> daftar ini yang Anda gunakan dalam adaptor.

Ramkesh Yadav
sumber
2

Bagi saya, ini berhasil setelah menambahkan baris kode ini:

mRecyclerView.setItemAnimator(null);
Fatih Gee
sumber
2
ini bukan perbaikan dalam kebanyakan kasus, jika Anda ingin animasi Anda harus menulis ulang kode adaptor Anda dan menemukan kesalahan Anda dalam memberitahukan perubahan
Dragos Rachieru
Ini berfungsi dengan baik untuk saya. Saya menggunakan adaptor nyata sehingga saya tidak dapat mengontrol aliran dan saya mengaktifkan windowActivityTransitions dalam gaya yang menyebabkan masalah ini terima kasih orang yang menyelamatkan hari saya.
Arul Mani
1

masalah ini dapat terjadi ketika Anda mencoba membersihkan daftar Anda, jika Anda akan menghapus daftar data Anda terutama ketika Anda menggunakan tarik untuk menyegarkan mencoba menggunakan bendera boolean, inisialisasi itu sebagai false dan di dalam metode OnRefresh membuatnya benar, bersihkan dataList Anda jika flag benar sebelum menambahkan data baru ke dalamnya dan setelah itu buatlah false.

kode Anda mungkin seperti ini

 private boolean pullToRefreshFlag = false ;
 private ArrayList<your object> dataList ;
 private Adapter adapter ;

 public class myClass extend Fragment implements SwipeRefreshLayout.OnRefreshListener{

 private void requestUpdateList() {

     if (pullToRefresh) {
        dataList.clear
        pullToRefreshFlag = false;
     }

     dataList.addAll(your data);
     adapter.notifyDataSetChanged;


 @Override
 OnRefresh() {
 PullToRefreshFlag = true
 reqUpdateList() ; 
 }

}
Moaz H
sumber
1

Saya memiliki masalah yang sama sebelumnya. Akhirnya menemukan solusi untuk itu

Apa yang saya lakukan adalah memberi tahu adaptor bahwa item telah dihapus dan kemudian memberitahukan kisaran data adaptor berubah

 public void setData(List<Data> dataList) {
      if (this.dataList.size() > 0) {
          notifyItemRangeRemoved(0, dataList.size());
          this.dataList.clear();
      }
      this.dataList.addAll(dataList)
      notifyItemRangeChanged(0, dataList.size());

 }
Cheng
sumber
1

Saya mengalami masalah serupa dan baru saja menemukan jawabannya. Saya membuat beberapa contoh hard case untuk beberapa test case tetapi tidak memastikan mereka masing-masing mengembalikan ID unik dan itu menyebabkan crash di bawah untuk saya. Memperbaiki ID menyelesaikan masalah, semoga ini membantu orang lain!

DocBot
sumber
1

saya pernah mendapat kesalahan juga:

Penyebab: Saya mencoba memperbarui Tampilan Pendaur Ulang dari tugas Async sementara secara bersamaan mencoba untuk mendapatkan viewHolders lama yang dihapus;

Kode: Saya menghasilkan data dengan menekan tombol, logikanya sebagai berikut

  1. Kosongkan item terakhir dalam tampilan pendaur ulang
  2. Panggil tugas async untuk menghasilkan data
  3. OnPostExecute Perbarui tampilan Pendaur Ulang dan NotifyDataSetChanged

Masalah: Setiap kali saya gulir cepat sebelum menghasilkan data saya saya dapatkan

Ketidakkonsistenan terdeteksi. Posisi adaptor pemegang pandangan tidak validViewHolder java.lang.IndexOutOfBoundsException: Ketidakkonsistenan terdeteksi. Posisi item 20 tidak valid (offset: 2) .state: 3

Solusi: daripada menghapus RecyclerView sebelum menghasilkan data saya, saya malah meninggalkannya dan kemudian Ganti dengan Data Baru, Panggilan NotifyDatasetChanged, seperti yang ditunjukkan di bawah ini;

       @Override
        protected void onPostExecute(List<Objects> o) {
            super.onPostExecute(o);
            recyclerViewAdapter.setList(o);
            mProgressBar.setVisibility(View.GONE);
            mRecyclerView.setVisibility(View.VISIBLE);
        }
EdgeDev
sumber
bisa tolong lihat kode saya, saya pikir masalah saya seperti milik Anda [tautan] ( stackoverflow.com/questions/50213362/... )
Mateen Chaudhry
1

Hapus saja semua tampilan Manajer tata letak Anda sebelum memberi tahu. Suka:

myLayoutmanager.removeAllViews();
BaBaX Ra
sumber
Berhasil. Saya punya masalah dengan memuat gulir dan perubahan tab.
Warwicky
1

Menggunakan ListAdapter (androidx.recyclerview.widget.ListAdapter)panggilan adapter.submitList(null)sebelum menelepon adapter.submitList(list):

adapter.submitList(null)
adapter.submitList(someDataList)
Sergey
sumber
1

Pengecualian ini muncul pada API 19, 21 (tetapi bukan yang baru). Di Kotlin coroutine saya memuat data (di utas latar) dan di utas UI ditambahkan dan menunjukkan kepada mereka:

adapter.addItem(item)

Adaptor:

var list: MutableList<Item> = mutableListOf()

init {
    this.setHasStableIds(true)
}

open fun addItem(item: Item) {
    list.add(item)
    notifyItemInserted(list.lastIndex)
}

Untuk beberapa alasan Android tidak membuat cukup cepat atau sesuatu yang lain, jadi, saya memperbarui daftar dengan postmetode RecyclerView(tambah, hapus, perbarui acara item):

view.recycler_view.post { adapter.addItem(item) }

Pengecualian ini mirip dengan "Tidak dapat memanggil metode ini dalam panggilan balik gulir. Panggilan balik gulir mungkin dijalankan selama pass pengukuran & tata letak di mana Anda tidak dapat mengubah data RecyclerView. Setiap panggilan metode yang mungkin mengubah struktur RecyclerView atau konten adaptor harus ditunda untuk frame berikutnya. ": Recyclerview - tidak dapat memanggil metode ini dalam panggilan balik gulir .

CoolMind
sumber
0

Saya menemukan pengaturan mRecycler.setLayoutFrozen (true); dalam metode onRefresh dari swipeContainer.

memecahkan masalah untuk saya.

swipeContainer.setOnRefreshListener(new   SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            orderlistRecycler.setLayoutFrozen(true);
            loadData(false);

        }
    });
Mark Sheekey
sumber
0

Ini adalah bug yang tidak menyenangkan.

Untuk menangani klik item saya, saya menggunakan implementasi yang RecyclerView.OnItemTouchListenermirip dengan solusi yang ditemukan dalam pertanyaan ini .

Setelah berkali-kali menyegarkan RecyclerViewsumber data dan mengklik suatu item, ini IndexOutOfBoundsExceptionakan merusak aplikasi saya. Ketika sebuah item diklik, secara RecyclerViewinternal mencari tampilan dasar yang benar dan mengembalikan posisi itu. Memeriksa kode sumber, saya melihat ada beberapa Tasksdan Threadsdijadwalkan. Singkatnya, pada dasarnya ini hanya beberapa keadaan ilegal di mana dua sumber data dicampur dan tidak disinkronkan dan semuanya menjadi liar.

Berdasarkan hal ini, saya dihapus implementasi saya dari RecyclerView.OnItemTouchListenerdan hanya tertangkap klik pada ViewHolderdari Adapterdiri sendiri:

public void onBindViewHolder (final BaseContentView holder, final int position) {

    holder.itemView.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick (View view) {

        // do whatever you like here
      }
    });

}

Ini mungkin bukan solusi terbaik, tetapi bebas crash untuk saat ini .. Semoga ini akan menghemat waktu Anda :).

DroidBender
sumber
Membuat objek baru setiap kali onBind dipanggil akan menghasilkan banyak objek yang akan dikumpulkan sampah dan pengguna mungkin mengalami pembekuan.
Dephinera