Подтвердить что ты не робот

Android RecyclerView: notifyDataSetChanged() IllegalStateException

Я пытаюсь обновить элементы recycleview, используя notifyDataSetChanged().

Это мой метод onBindViewHolder() в адаптере 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();
        }
    });
}

Что я хочу сделать, это обновить элементы списка после проверки флажка. Я получаю незаконное исключение: "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)

Я также использовал notifyItemChanged(), такое же исключение. Любой секретный способ обновления уведомлять адаптер о том, что что-то изменилось?

4b9b3361

Ответ 1

Вам следует переместить метод 'SetOnCheckedChangeListener()' в ViewHolder, который является внутренним классом на вашем адаптере.

onBindViewHolder() не является методом инициализации ViewHolder. Этот метод является шагом обновления каждого элемента ресайклинга. Когда вы вызываете notifyDataSetChanged(), onBindViewHolder() будет вызываться как количество каждого времени элемента.

Итак, если вы notifyDataSetChanged() поместили в onCheckChanged() и инициализировали checkBox в onBindViewHolder(), вы получите IllegalStateException из-за кругового вызова метода.

click checkbox → onCheckedChanged() → notifyDataSetChanged() → onBindViewHolder() → установить флажок → onChecked...

Просто вы можете исправить это, вставив один флаг в адаптер.

попробуйте это,

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

Ответ 2

С помощью Handler для добавления элементов и вызова notify...() из этого Handler исправлена ​​проблема для меня.

Ответ 3

Вы можете просто reset предыдущий прослушиватель, прежде чем вносить изменения, и вы не получите это исключение.

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

Ответ 4

Я не очень хорошо знаю, но у меня тоже была такая же проблема. Я решил это, используя onClickListner on checkbox

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

Попробуйте это, это может помочь!

Ответ 5

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

Ответ 6

Найдено простое решение -

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

Здесь мы создаем runnable to notifyItemChanged для позиции, когда recyclerview готов к ее обработке.

Ответ 7

Если у вас есть сообщение об ошибке:

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

Просто, просто делайте то, что вызывает исключение:

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

Ответ 8

Сначала я подумал, что ответ Moonsoo (принятый ответ) не будет работать для меня, потому что я не могу инициализировать мой setOnCheckedChangeListener() в конструкторе ViewHolder, потому что мне нужно свяжите его каждый раз, чтобы получить обновленную переменную положения. Но мне потребовалось много времени, чтобы понять, что он говорил.

Вот пример "кругового вызова метода", о котором он говорит:

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

Единственная проблема с этим заключается в том, что когда нам нужно инициализировать переключатель, который должен быть включен или выключен (например, из прошлого сохраненного состояния), он вызывает слушателя, который может вызвать nofityItemRangeChanged, который вызывает onBindViewHolder снова, Вы не можете вызвать onBindViewHolder, когда вы уже находитесь в onBindViewHolder], потому что вы не можете notifyItemRangeChanged, если вы уже находитесь в середине уведомления о том, что диапазон элементов изменился. Но мне нужно было только обновить пользовательский интерфейс, чтобы показывать его вкл. или выкл., не желая фактически запускать что-либо.

Вот решение, которое я узнал из ответа JoniDS, который предотвратит бесконечный цикл. Пока мы устанавливаем прослушиватель на "null" до того, как мы установим "Проверено", он обновит пользовательский интерфейс без запуска слушателя, избегая бесконечного цикла. Затем мы можем установить прослушиватель после.

Код JoniDS:

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

Полное решение для моего примера:

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

Ответ 9

элемент CheckBox изменяет значение, когда вы вызываете notifyDataSetChanged();, чтобы это исключение произошло. Попробуйте вызвать notifyDataSetChanged(); в сообщении вашего представления. Например:

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

Ответ 10

В то время как элемент привязан менеджером компоновки, очень вероятно, что вы устанавливаете проверенное состояние вашего флажка, который вызывает обратный вызов.

Конечно, это предположение, потому что вы не опубликовали полную трассировку стека.

Вы не можете изменить содержимое адаптера, в то время как RV пересчитывает макет. Вы можете избежать этого, не вызвав notifyDataSetChanged, если состояние элемента проверено равным значению, отправленному в обратном вызове (что будет иметь место, если вызов checkbox.setChecked запускает обратный вызов).

Ответ 11

Использовать onClickListner на флажке вместо OnCheckedChangeListener, он решит проблему

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

Ответ 12

До notifyDataSetChanged() просто проверьте, что с помощью этого метода: recyclerView.IsComputingLayout()

Ответ 13

Почему бы не проверить состояние RecyclerView.isComputingLayout() следующим образом?

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

Ответ 14

Простое использование Post:

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

Ответ 15

Я столкнулся с этой точной проблемой! После того, как ответ Moonsoo действительно не плавал на моей лодке, я немного пошутил и нашел решение, которое сработало для меня.

Во-первых, вот некоторые из моих кодов:

    @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());
            }
        }
    });

Вы заметите, что я специально уведомляю адаптер о позиции, которую я меняю, вместо всего набора данных, как вы делаете. При этом, хотя я не могу гарантировать, что это сработает для вас, я решил проблему, завернув мой вызов notifyItemChanged() в блок try/catch. Это просто застигнуло исключение, но все же позволило моему адаптеру зарегистрировать изменение состояния и обновить отображение!

Надеюсь, это поможет кому-то!

EDIT: Я признаю, что это, вероятно, не правильный/зрелый способ справиться с этой проблемой, но поскольку он, похоже, не вызывает никаких проблем, оставляя исключение необработанным, я думал, что буду делиться случаем это было достаточно для кого-то другого.

Ответ 16

Это происходит потому, что вы, вероятно, устанавливаете "слушатель", прежде чем настраивать значение для этой строки, что заставляет слушателя запускаться при настройке значения для этого флажка.

Что вам нужно сделать:

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

Ответ 17

        @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 {

                    }

                }
            };
        }

Ответ 18

Для меня я слушал изменения в рейтинге в строке рейтинга, но при длительном нажатии нескольких кликов одновременно приложение зависало из-за проблемы, а затем нашел ясное решение, если бы я хотел notifydatasetchange(); в bindviewholder обработал его обработчиком, данным:

//inside bindViewHolder                 
new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        notifyDataSetChanged();
                    }
                });

Надеюсь, что это решит проблему.

Ответ 19

просто используйте isPressed() метод CompoundButton в onCheckedChanged(CompoundButton compoundButton, boolean isChecked)
например

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

Ответ 20

Для меня проблема возникла, когда я вышел из EditText с помощью кнопки "Готово", "Назад" или касания внешнего ввода. Это приводит к обновлению модели с помощью входного текста, а затем обновлению представления переработчика с помощью наблюдения данных в реальном времени.

Проблема заключалась в том, что курсор/фокус остаются в EditText.

Когда я удалил фокус с помощью:

editText.clearFocus() 

Оповещение об изменении данных. Метод просмотра перезапуска перестал выдавать эту ошибку.

Я думаю, что это одна из возможных причин/решений этой проблемы. Вполне возможно, что это исключение можно исправить по-другому, так как оно может быть вызвано совершенно другой причиной.

Ответ 21

Внимание! Это исключение, вероятно, означает, что вы обновляете адаптер не из UI-потока ! Например, вы заполняете адаптер из RxJava и забыли добавить .observeOn(AndroidSchedulers.mainThread()).

Если вы уверены, что этого не сделали, посмотрите решение на Kotlin (спасибо @bruce):

private var recyclerView: RecyclerView? = null

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
    super.onAttachedToRecyclerView(recyclerView)
    this.recyclerView = recyclerView
}

fun setNewItem(position: Int, text: String) {
    // Your changes to an item start here.
    // ...
    if (recyclerView?.isComputingLayout == false) {
        notifyItemChanged(position)
    } else {
        recyclerView?.handler?.post { notifyItemChanged(position) }
    }
}