Android RecyclerView: notifyDataSetChanged () IllegalStateException


130

Saya mencoba memperbarui item dari recycleview menggunakan notifyDataSetChanged ().

Ini adalah metode onBindViewHolder () saya di adaptor recycleview.

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {

     //checkbox view listener
    viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

            //update list items
            notifyDataSetChanged();
        }
    });
}

Yang ingin saya lakukan adalah memperbarui daftar item, setelah saya centang kotak centang. Saya mendapatkan pengecualian ilegal:"Cannot call this method while RecyclerView is computing a layout or scrolling"

java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
    at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:1462)
    at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onChanged(RecyclerView.java:2982)
    at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:7493)
    at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:4338)
    at com.app.myapp.screens.RecycleAdapter.onRowSelect(RecycleAdapter.java:111)

Saya juga menggunakan notifyItemChanged (), pengecualian yang sama. Adakah cara rahasia untuk memperbarui untuk memberi tahu adaptor bahwa ada sesuatu yang berubah?


Saya memiliki masalah yang sama sekarang. menempatkan pendengar setoncheckchanged di viewholder konstruktor memberi saya kesalahan yang sama
filthy_wizard

Jawaban:


145

Anda harus memindahkan metode 'setOnCheckedChangeListener ()' ke ViewHolder yang merupakan kelas dalam pada adaptor Anda.

onBindViewHolder()bukan metode yang menginisialisasi ViewHolder. Metode ini adalah langkah menyegarkan setiap item pendaur ulang. Saat Anda menelepon notifyDataSetChanged(), onBindViewHolder()akan dipanggil sebagai jumlah setiap item kali.

Jadi Jika Anda notifyDataSetChanged()memasukkan onCheckChanged()dan menginisialisasi kotak centang onBindViewHolder(), Anda akan mendapatkan IllegalStateException karena panggilan metode melingkar.

klik kotak centang -> onCheckedChanged () -> notifyDataSetChanged () -> onBindViewHolder () -> setel kotak centang -> onChecked ...

Sederhananya, Anda dapat memperbaiki ini dengan memasukkan satu bendera ke Adaptor.

coba ini,

private boolean onBind;

public ViewHolder(View itemView) {
    super(itemView);
    mCheckBox = (CheckBox) itemView.findViewById(R.id.checkboxId);
    mCheckBox.setOnCheckChangeListener(this);
}

@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    if(!onBind) {
        // your process when checkBox changed
        // ...

        notifyDataSetChanged();
    }
}

...

@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
    // process other views 
    // ...

    onBind = true;
    viewHolder.mCheckBox.setChecked(trueOrFalse);
    onBind = false;
}

Begitu ya, masuk akal. Berharap platform akan memprediksi perilaku sederhana dan memberikan solusi daripada harus bergantung pada bendera ..
Arthur

Tidak masalah di mana Anda mengatur pendengar selama Anda tidak memberi tahu saat AdapterViewObserversedang onBindViewHolder()berlangsung.
Yaroslav Mytkalyk

6
Saya lebih suka solusi ini stackoverflow.com/a/32373999/1771194 dengan beberapa peningkatan dalam komentar. Itu juga memungkinkan saya membuat "RadioGroup" di RecyclerView.
Artem

bagaimana cara mendapatkan posisi item dalam daftar saya ??
filthy_wizard

2
ini tidak bekerja untuk saya di pemirsa. masih mendapatkan error crash. saya perlu mengubah vars di daftar array. sangat aneh. tidak terlalu yakin di mana saya bisa melampirkan listiner.
filthy_wizard

45

Anda bisa mereset pendengar sebelumnya sebelum membuat perubahan dan Anda tidak akan mendapatkan pengecualian ini.

private CompoundButton.OnCheckedChangeListener checkedListener = new CompoundButton.OnCheckedChangeListener() {                      
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                            //Do your stuff
                    });;

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        holder.checkbox.setOnCheckedChangeListener(null);
        holder.checkbox.setChecked(condition);
        holder.checkbox.setOnCheckedChangeListener(checkedListener);
    }

2
Jawaban yang bagus, tetapi lebih baik untuk tidak membuat pendengar pada setiap panggilan onBindViewHolder. Jadikan sebagai bidang.
Artem

1
Menggunakan bidang tentu saja lebih baik, saya hanya memberikan contoh yang berfungsi. Tapi terima kasih atas peringatannya, saya akan memperbarui jawabannya.
JoniDS

1
Sebenarnya saya perlu mengikat pendengar baru setiap kali, karena pendengar perlu variabel posisi yang diperbarui setiap kali. Jadi ini adalah jawaban yang bagus jadi saya tidak perlu menggunakan Handler.
Rock Lee

Ini jelas merupakan cara terbaik untuk melakukannya karena tidak pernah direkomendasikan untuk mempertahankan keadaan global, yang direkomendasikan oleh jawaban ( stackoverflow.com/a/31069171/882251 ).
Darwind

Yang paling sederhana !! Terima kasih !!
DalveerSinghDaiya

39

Menggunakan Handleruntuk menambahkan item dan menelepon notify...()dari ini Handlermemperbaiki masalah bagi saya.


3
Itu jawaban yang tepat, Anda tidak dapat mengubah item saat pengaturan (dengan memanggil onBindViewHolder). Dalam hal ini Anda harus memanggil notifyDataSetChanged pada akhir loop saat ini dengan memanggil Handler.post ()
pjanecze

1
@ user1232726 Jika Anda membuat Handler di utas utama, Anda tidak harus menentukan Looper (default ke looper utas panggilan). Jadi ya, ini saran saya. Kalau tidak, Anda juga dapat menentukan Looper secara manual.
cybergen

sayangnya kotak centang saya tidak tetap diperiksa ketika saya gulir ke bawah dan gulir kembali ke atas. boo. lol
filthy_wizard

@ user1232726 cari jawaban atau ajukan pertanyaan baru yang menjelaskan masalah Anda.
cybergen

2
Saya akan sangat menyarankan jawaban ini karena ini adalah cara hacky untuk memecahkan masalah. Semakin Anda melakukan ini semakin kode Anda menjadi kompleks untuk dipahami. Lihat jawaban Moonsoo untuk memahami masalah dan jawaban JoniDS untuk menyelesaikan masalah.
Kalpesh Patel

26

Saya tidak tahu, tetapi saya juga memiliki masalah yang sama. Aku memecahkan ini dengan menggunakan onClickListnerpadacheckbox

viewHolder.mCheckBox.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            if (model.isCheckboxBoolean()) {
                model.setCheckboxBoolean(false);
                viewHolder.mCheckBox.setChecked(false);
            } else {
                model.setCheckboxBoolean(true);
                viewHolder.mCheckBox.setChecked(true);
            }
            notifyDataSetChanged();
        }
    });

Coba ini, ini bisa membantu!


1
Kerja bagus) TAPI hanya pada klik (jika saya memperlambat widget bergerak (SwitchCompat), tindakan ini akan dilewatkan. Ini adalah satu-satunya masalah
Vlad

12
protected void postAndNotifyAdapter(final Handler handler, final RecyclerView recyclerView, final RecyclerView.Adapter adapter) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (!recyclerView.isComputingLayout()) {
                    adapter.notifyDataSetChanged();
                } else {
                    postAndNotifyAdapter(handler, recyclerView, adapter);
                }
            }
        });
    }

Saya kira Anda dapat dengan mudah memberi tahu adaptor dua kali.
Максим Петлюк

8

Ketika Anda memiliki Pesan Kesalahan:

Cannot call this method while RecyclerView is computing a layout or scrolling

Sederhana, Lakukan saja apa yang menyebabkan Pengecualian di:

RecyclerView.post(new Runnable() {
    @Override
    public void run() {
        /** 
        ** Put Your Code here, exemple:
        **/
        notifyItemChanged(position);
    }
});

1
Ini berhasil untuk saya. Ingin tahu apakah ada masalah dengan solusi ini?
Sayooj Valsan

7

Menemukan solusi sederhana -

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    private RecyclerView mRecyclerView; 

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
    }

    private CompoundButton.OnCheckedChangeListener checkedChangeListener 
    = (compoundButton, b) -> {
        final int position = (int) compoundButton.getTag();
        // This class is used to make changes to child view
        final Event event = mDataset.get(position);
        // Update state of checkbox or some other computation which you require
        event.state = b;
        // we create a runnable and then notify item changed at position, this fix crash
        mRecyclerView.post(new Runnable() {
            @Override public void run() {
                notifyItemChanged(position));
            }
        });
    }
}

Di sini kita membuat runnable untuk memberitahukanItemChanged untuk posisi ketika recyclerview siap menanganinya.


5

item CheckBox Anda sedang dalam perubahan yang dapat ditarik saat Anda menelepon notifyDataSetChanged();sehingga pengecualian ini akan terjadi. Coba panggil notifyDataSetChanged();pos Anda. Sebagai contoh:

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

4

Pada awalnya saya pikir jawaban Moonsoo ( jawaban yang diterima) tidak akan berfungsi untuk saya karena saya tidak dapat menginisialisasi saya setOnCheckedChangeListener()di konstruktor ViewHolder karena saya harus mengikatnya setiap kali sehingga mendapat variabel posisi yang diperbarui. Tapi aku butuh waktu lama untuk menyadari apa yang dia katakan.

Berikut adalah contoh dari "panggilan metode melingkar" yang ia bicarakan:

public void onBindViewHolder(final ViewHolder holder, final int position) {
    SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);
    mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                       if (isChecked) {
                           data.delete(position);
                           notifyItemRemoved(position);
                           //This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!
                           notifyItemRangeChanged(position, data.size());
                       }
                   }
            });
    //Set the switch to how it previously was.
    mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.
}

Satu-satunya masalah dengan ini, adalah bahwa ketika kita perlu menginisialisasi sakelar untuk hidup atau mati (dari keadaan tersimpan sebelumnya, misalnya), itu memanggil pendengar yang mungkin memanggil nofityItemRangeChangedpanggilan yang onBindViewHolderlagi. Anda tidak dapat menelepon onBindViewHoldersaat Anda sudah berada di onBindViewHolder], karena Anda tidak bisa notifyItemRangeChangedjika Anda sudah memberitahukan bahwa kisaran item telah berubah. Tapi saya hanya perlu memperbarui UI untuk mengaktifkan atau menonaktifkannya, tidak ingin memicu apa pun.

Inilah solusi yang saya pelajari dari jawaban JoniDS yang akan mencegah loop tak terbatas. Selama kita mengatur pendengar menjadi "null" sebelum kita mengatur Diperiksa, maka itu akan memperbarui UI tanpa memicu pendengar, menghindari loop tak terbatas. Lalu kita bisa mengatur pendengar setelah.

Kode JoniDS:

holder.checkbox.setOnCheckedChangeListener(null);
holder.checkbox.setChecked(condition);
holder.checkbox.setOnCheckedChangeListener(checkedListener);

Solusi lengkap untuk contoh saya:

public void onBindViewHolder(final ViewHolder holder, final int position) {
    SwitchCompat mySwitch = (SwitchCompat) view.findViewById(R.id.switch);

    //Set it to null to erase an existing listener from a recycled view.
    mySwitch.setOnCheckedChangeListener(null);

    //Set the switch to how it previously was without triggering the listener.
    mySwitch.setChecked(savedSwitchState); //If the saved state was "true", then this will trigger the infinite loop.

    //Set the listener now.
    mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked) {
                data.delete(position);
                notifyItemRemoved(position);
                //This will call onBindViewHolder, but we can't do that when we are already in onBindViewHolder!
                notifyItemRangeChanged(position, data.size());
            }
        }
    });
}

Anda harus menghindari menginisialisasi OnCheckedChangeListener berulang-ulang di onBindViewHolder (kurang GC dibutuhkan dengan cara ini). Ini seharusnya dipanggil di onCreateViewHolder, dan Anda mendapatkan posisinya dengan memanggil holder.getAdapterPosition ().
pengembang android

4

Mengapa tidak memeriksa RecyclerView.isComputingLayout()keadaan sebagai berikut?

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    private RecyclerView mRecyclerView; 

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        mRecyclerView = recyclerView;
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {

        viewHolder.getCheckbox().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (mRecyclerView != null && !mRecyclerView.isComputingLayout()) {
                    notifyDataSetChanged();
                }
            }
        });
    }
}

2

Sementara item sedang terikat oleh manajer tata letak, sangat mungkin bahwa Anda sedang mengatur keadaan dicentang kotak Anda, yang memicu panggilan balik.

Tentu saja ini dugaan karena Anda tidak mempublikasikan jejak tumpukan penuh.

Anda tidak dapat mengubah konten adaptor saat RV menghitung ulang tata letak. Anda dapat menghindarinya dengan tidak memanggil notifyDataSetChanged jika status item yang dicentang sama dengan nilai yang dikirim dalam panggilan balik (yang akan terjadi jika panggilan checkbox.setCheckedmemicu panggilan balik).


Terima kasih @yigit! Masalah saya tidak ada hubungannya dengan kotak centang tetapi situasi yang lebih kompleks di mana saya harus memberi tahu item yang berbeda di adaptor, tetapi saya mendapatkan crash yang sama. Saya memperbarui logika pemberitahuan saya untuk hanya memperbarui ketika data benar-benar berubah dan itu menyelesaikan kecelakaan saya. Jadi aturan baru saya dengan RecyclerViews: jangan beri tahu bahwa ada sesuatu yang berubah ketika tidak ada yang berubah. Terima kasih banyak atas jawaban ini!
CodyEngel

2

Gunakan onClickListner pada kotak centang alih-alih OnCheckedChangeListener, Ini akan menyelesaikan masalah

viewHolder.myCheckBox.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (viewHolder.myCheckBox.isChecked()) {
                // Do something when checkbox is checked
            } else {
                // Do something when checkbox is unchecked                
            }
            notifyDataSetChanged();
        }
    });


1

Penggunaan sederhana Posting:

new Handler().post(new Runnable() {
        @Override
        public void run() {
                mAdapter.notifyItemChanged(mAdapter.getItemCount() - 1);
            }
        }
    });

0

Saya mengalami masalah persis ini! Setelah jawaban Moonsoo tidak benar-benar mengapungkan kapalku, aku sedikit mengacau dan menemukan solusi yang berhasil untukku.

Pertama, ini beberapa kode saya:

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {

    final Event event = mDataset.get(position);

    //
    //  .......
    //

    holder.mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            event.setActive(isChecked);
            try {
                notifyItemChanged(position);
            } catch (Exception e) {
                Log.e("onCheckChanged", e.getMessage());
            }
        }
    });

Anda akan melihat bahwa saya secara khusus memberi tahu adaptor untuk posisi yang saya ubah, alih-alih seluruh dataset seperti yang Anda lakukan. Yang sedang berkata, meskipun saya tidak dapat menjamin ini akan bekerja untuk Anda, saya menyelesaikan masalah dengan membungkus notifyItemChanged()panggilan saya di blok try / catch. Ini hanya menangkap pengecualian, tetapi masih memungkinkan adaptor saya untuk mendaftarkan perubahan negara dan memperbarui tampilan!

Semoga ini bisa membantu seseorang!

EDIT: Saya akui, ini mungkin bukan cara yang tepat / matang untuk menangani masalah ini, tetapi karena tampaknya tidak menyebabkan masalah dengan membiarkan pengecualian tidak ditangani, saya pikir saya akan berbagi jika itu baik cukup untuk orang lain.


0

Ini terjadi karena Anda mungkin mengatur 'pendengar' sebelum Anda mengkonfigurasi nilai untuk baris itu, yang membuat pendengar menjadi terpicu ketika Anda 'mengkonfigurasi nilai' untuk kotak centang.

Yang perlu Anda lakukan adalah:

@Override
public void onBindViewHolder(YourAdapter.ViewHolder viewHolder, int position) {
   viewHolder.mCheckBox.setOnCheckedChangeListener(null);
   viewHolder.mCheckBox.setChecked(trueOrFalse);
   viewHolder.setOnCheckedChangeListener(yourCheckedChangeListener);
}

0
        @Override
        public void onBindViewHolder(final MyViewHolder holder, final int position) {
            holder.textStudentName.setText(getStudentList.get(position).getName());
            holder.rbSelect.setChecked(getStudentList.get(position).isSelected());
            holder.rbSelect.setTag(position); // This line is important.
            holder.rbSelect.setOnClickListener(onStateChangedListener(holder.rbSelect, position));

        }

        @Override
        public int getItemCount() {
            return getStudentList.size();
        }
        private View.OnClickListener onStateChangedListener(final RadioButton checkBox, final int position) {
            return new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (checkBox.isChecked()) {
                        for (int i = 0; i < getStudentList.size(); i++) {

                            getStudentList.get(i).setSelected(false);

                        }
                        getStudentList.get(position).setSelected(checkBox.isChecked());

                        notifyDataSetChanged();
                    } else {

                    }

                }
            };
        }

0

cukup gunakan isPressed()metode CompoundButtondalam onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
misalnya

public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {   
                      ... //your functionality    
                            if(compoundButton.isPressed()){
                                notifyDataSetChanged();
                            }
                        }  });

0

Saya memiliki masalah yang sama menggunakan Kotak Centang dan RadioButton. Mengganti notifyDataSetChanged()dengan notifyItemChanged(position)bekerja. Saya menambahkan bidang Boolean isCheckedke model data. Kemudian saya memperbarui nilai Boolean dan onCheckedChangedListener, saya menelepon notifyItemChanged(adapterPosition). Ini mungkin bukan cara terbaik, tetapi berhasil untuk saya. Nilai boolean digunakan untuk memeriksa apakah item dicentang.


0

kebanyakan itu terjadi karena notifydatasetchang memanggil peristiwa checkbox berubah dan dalam acara itu lagi ada notifydatasetchanged .

untuk menyelesaikannya, Anda cukup memeriksa bahwa kotak centang dicentang secara terprogram atau pengguna menekannya. ada metode yang ditekan untuk itu.

jadi bungkus seluruh kode daftar di dalam metode isPressed. dan itu dilakukan.

 holder.mBinding.cbAnnual.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {

                if(compoundButton.isPressed()) {


                       //your code
                        notifyDataSetChanged();   

            }
        });

0

Saya menderita masalah ini selama berjam-jam dan ini adalah bagaimana Anda dapat memperbaikinya. Tetapi sebelum Anda mulai ada beberapa syarat untuk solusi ini.

MODEL CLASS

public class SelectUserModel {

    private String userName;
    private String UserId;
    private Boolean isSelected;


    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserId() {
        return UserId;
    }

    public void setUserId(String userId) {
        UserId = userId;
    }

    public Boolean getSelected() {
        return isSelected;
    }

    public void setSelected(Boolean selected) {
        isSelected = selected;
    }
}

CHECKBOX dalam ADAPTER CLASS

CheckBox cb;

KONSTRUKTOR KELAS ADAPTOR & DAFTAR MODEL

private List<SelectUserModel> userList;

public StudentListAdapter(List<SelectUserModel> userList) {
        this.userList = userList;

        for (int i = 0; i < this.userList.size(); i++) {
            this.userList.get(i).setSelected(false);
        }
    }

ONBINDVIEW [Silakan gunakan onclick sebagai ganti onCheckChange]

public void onBindViewHolder(@NonNull final StudentListAdapter.ViewHolder holder, int position) {
    holder.cb.setChecked(user.getSelected());
    holder.cb.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {

            int pos = (int) view.getTag();
            Log.d(TAG, "onClick: " + pos);
            for (int i = 0; i < userList.size(); i++) {
                if (i == pos) {
                    userList.get(i).setSelected(true);
// an interface to listen to callbacks
                    clickListener.onStudentItemClicked(userList.get(i));
                } else {
                    userList.get(i).setSelected(false);
                }
            }
            notifyDataSetChanged();
        }
    });

}


-1

Bagi saya masalah terjadi ketika saya keluar dari EditText oleh Done, Back, atau input luar sentuh. Hal ini menyebabkan pembaruan model dengan teks input, kemudian refresh tampilan pendaur ulang melalui pengamatan data langsung.

Masalahnya adalah bahwa kursor / fokus tetap di EditText.

Ketika saya telah menghapus fokus dengan menggunakan:

editText.clearFocus() 

Beritahu data perubahan metode tampilan pendaur ulang tidak berhenti melempar kesalahan ini.

Saya pikir ini adalah salah satu kemungkinan alasan / solusi untuk masalah ini. Ada kemungkinan bahwa pengecualian ini dapat diperbaiki dengan cara lain karena dapat disebabkan oleh alasan yang sama sekali berbeda.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.