Android RecyclerView : notifyDataSetChanged () IllegalStateException


130

notifyDataSetChanged ()를 사용하여 recycleview의 항목을 업데이트하려고합니다.

이것은 recycleview 어댑터의 내 onBindViewHolder () 메소드입니다.

@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 ()와 동일한 예외를 사용했습니다. 변경 사항을 어댑터에 알리기 위해 비밀리에 업데이트하는 방법이 있습니까?


지금이 동일한 문제가 발생했습니다. viewholder 생성자주고 나에게 동일한 오류에 setoncheckchanged 수신기를 넣어
filthy_wizard

답변:


145

'setOnCheckedChangeListener ()'메소드를 어댑터의 내부 클래스 인 ViewHolder로 이동해야합니다.

onBindViewHolder()초기화하는 메소드가 아닙니다 ViewHolder. 이 방법은 각 재활용품 항목을 새로 고치는 단계입니다. 당신이 호출 할 때 notifyDataSetChanged(), onBindViewHolder()각 항목의 횟수로 호출됩니다.

따라서 checkBox 를 notifyDataSetChanged()넣고 onCheckChanged()초기화 onBindViewHolder()하면 순환 메서드 호출로 인해 IllegalStateException이 발생합니다.

확인란-> 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;
}

알겠습니다. 플랫폼이 그러한 간단한 행동을 예측하고 깃발에 의존하는 대신 솔루션을 제공하기를 바랍니다.
Arthur

진행중인 AdapterViewObserver동안 알리지 않는 한 리스너를 어디에 설정했는지는 중요하지 않습니다 onBindViewHolder().
Yaroslav Mytkalyk

6
의견이 약간 개선 된 이 솔루션 stackoverflow.com/a/32373999/1771194 를 선호합니다 . 또한 RecyclerView에서 "RadioGroup"을 만들 수있었습니다.
Artem

내 목록에서 아이템 위치를 어떻게 얻습니까?
filthy_wizard

2
이것은 뷰 홀더에서 작동하지 않습니다. 여전히 충돌 오류가 발생합니다. arraylist에서 vars를 변경해야합니다. 아주 이상한. 내가 listiner를 어디에 연결할 수 있는지 확실하지 않습니다.
filthy_wizard

45

변경하기 전에 이전 리스너를 재설정하면이 예외가 발생하지 않습니다.

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
좋은 대답이지만 각 onBindViewHolder 호출에서 리스너를 작성하지 않는 것이 좋습니다. 필드로 만드십시오.
Artem

1
필드를 사용하는 것이 더 낫습니다. 저는 작동하는 예제를 제공했습니다. 그러나 경고 해 주셔서 감사합니다. 답변을 업데이트하겠습니다.
JoniDS

1
청취자는 매번 업데이트 된 위치 변수가 필요하기 때문에 실제로 어쨌든 매번 새로운 청취자를 바인딩해야합니다. 따라서 이것은 훌륭한 답변이므로 핸들러를 사용할 필요가 없습니다.
Rock Lee

이것이 허용되는 답변 ( stackoverflow.com/a/31069171/882251 )이 권장 하는 전역 상태를 유지하는 것은 결코 권장하지 않기 때문에 이것이 최선의 방법입니다 .
Darwind

가장 간단한 것 !! 감사 !!
DalveerSinghDaiya

39

사용 a Handler항목을 추가하고 호출하기위한 notify...()이에서 Handler나를 위해 문제를 해결했습니다.


3
정답입니다. 설정하는 동안 항목을 변경할 수 없습니다 (onBindViewHolder 호출). 이 경우 당신은 Handler.post ()를 호출하여 전류 루프의 끝에서 notifyDataSetChanged를 호출해야
pjanecze

1
@ user1232726 기본 스레드에서 핸들러를 작성하는 경우 루퍼를 지정할 필요가 없습니다 (기본값은 호출 스레드 루퍼입니다). 그렇습니다, 이것은 나의 충고입니다. 그렇지 않으면 루퍼를 수동으로 지정할 수도 있습니다.
cybergen

불행히도 아래로 스크롤하고 다시 위로 스크롤 할 때 내 확인란이 선택된 상태로 유지되지 않습니다. 우우. lol
filthy_wizard 10

@ user1232726은 답변을 검색하거나 문제를 설명하는 새로운 질문을합니다.
cybergen

2
이것이 문제를 해결하는 해키 방법 이므로이 답변을 강력히 권장하지 않습니다 . 더 많이할수록 코드를 이해하기가 복잡해집니다. 참조 Moonsoo의 답변을 문제를 이해할 수 JoniDS의 문제를 해결하기 답을.
Kalpesh Patel 2016 년

26

잘 모르겠지만 같은 문제가있었습니다. 나는 이것을 사용하여 이것을 해결 onClickListner했다.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();
        }
    });

이것을 시도하십시오, 이것은 도움이 될 수 있습니다!


1
일 니스) 만 클릭에 (내가 느린 이동 위젯 (SwitchCompat 경우), missed.This 될 것입니다이 작업이 유일한 문제입니다
블라드

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

어댑터를 두 번 쉽게 알릴 수 있다고 가정합니다.
Максим Петлюк

8

메시지 오류가 발생한 경우 :

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

1
이것은 나를 위해 일했습니다. 이 솔루션에 문제가 있습니까?
Sayooj Valsan

7

간단한 해결책을 찾았습니다.

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

여기에서 recyclerview가 처리 할 준비가되면 위치에 대해 itemItemd를 통지하는 실행 파일을 만듭니다.


5

CheckBox 항목이 호출 할 때 드로어 블이 변경 되어이 notifyDataSetChanged();예외가 발생합니다. notifyDataSetChanged();당신의 견해에 따라 전화 하십시오. 예를 들어 :

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

4

처음에 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 아니기 때문 입니다. 그러나 UI를 업데이트하거나 표시하기 위해 UI를 업데이트하기 만하면 실제로 아무것도 트리거하지 않으려 고했습니다.notifyItemRangeChanged

다음은 무한 루프를 방지하는 JoniDS의 답변 에서 배운 솔루션 입니다. Checked를 설정하기 전에 리스너를 "null"로 설정하면 무한 루프를 피하면서 리스너를 트리거하지 않고 UI를 업데이트합니다. 그런 다음 청취자를 설정할 수 있습니다.

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

onBindViewHolder에서 OnCheckedChangeListener를 반복해서 초기화하지 않아야합니다 (이 방법으로 GC가 덜 필요함). 이것은 onCreateViewHolder에서 호출되어야하며 holder.getAdapterPosition ()을 호출하여 위치를 가져옵니다.
안드로이드 개발자

4

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

2

항목이 레이아웃 관리자에 의해 바인딩되는 동안 체크 박스의 체크 상태를 설정하고 콜백을 유발할 가능성이 큽니다.

물론 이것은 전체 스택 추적을 게시하지 않았기 때문에 추측입니다.

RV가 레이아웃을 다시 계산하는 동안 어댑터 내용을 변경할 수 없습니다. 항목의 체크 상태가 콜백에서 전송 된 값과 같으면 notifyDataSetChanged를 호출하지 않으면이를 피할 수 있습니다 ( checkbox.setChecked콜백이 콜백을 트리거하는 경우에 해당함).


감사합니다 @yigit! 내 문제는 확인란과 관련이 없었지만 어댑터의 다른 항목을 알려야하는보다 복잡한 상황이지만 비슷한 충돌이 발생했습니다. 데이터가 실제로 변경 될 때만 업데이트되도록 알림 로직을 업데이트하고 충돌이 해결되었습니다. 따라서 RecyclerViews의 새로운 규칙 : 아무것도 변경되지 않았을 때 변경된 내용을 알리지 마십시오. 이 답변에 감사드립니다!
CodyEngel

2

OnCheckedChangeListener 대신 onClickListner 확인란을 사용하면 문제가 해결됩니다.

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

간단한 사용 포스트 :

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

0

나는이 정확한 문제에 부딪쳤다! 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 블록으로 전화를 감싸서 문제를 해결했습니다 . 이것은 단순히 예외를 잡았지만 여전히 어댑터가 상태 변경을 등록하고 디스플레이를 업데이트하도록 허용했습니다!

이것이 누군가를 돕기를 바랍니다!

편집 : 인정할 것입니다. 이것은 아마도 문제를 처리하는 적절한 / 성숙한 방법은 아니지만 예외를 처리하지 않고 문제가 발생하지 않는 것 같아서 좋을 경우 공유 할 것이라고 생각했습니다. 다른 사람에게는 충분합니다.


0

이것은 행에 대한 값을 구성하기 전에 '리스너'를 설정하고 있기 때문에 발생합니다. 확인란의 '값을 구성'할 때 리스너가 트리거됩니다.

당신이해야 할 일은 :

@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

간단하게 사용하는 isPressed()방법 CompoundButtononCheckedChanged(CompoundButton compoundButton, boolean isChecked)
예를

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

0

Checkbox와 RadioButton을 사용하는 것과 같은 문제가있었습니다. 교체 notifyDataSetChanged()notifyItemChanged(position)했다. isChecked데이터 모델에 부울 필드 를 추가했습니다 . 그런 다음 부울 값을 업데이트하고에서을 onCheckedChangedListener호출했습니다 notifyItemChanged(adapterPosition). 이것은 최선의 방법은 아니지만 나를 위해 일했습니다. 부울 값은 항목 확인 여부를 확인하는 데 사용됩니다.


0

대부분은 beacause를 발생 notifydatasetchanged 호출이 onCheckedchanged 이벤트 확인란의 및 해당 이벤트에 다시 거기 notifydatasetchanged .

이를 해결하기 위해 프로그래밍 방식으로 확인란을 선택했는지 또는 사용자가 체크했는지 확인할 수 있습니다. 방법은 isPressed 입니다.

그래서 전체 리스터 코드를 isPressed 메소드 안에 넣습니다. 그리고 그 일.

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

                if(compoundButton.isPressed()) {


                       //your code
                        notifyDataSetChanged();   

            }
        });

0

나는이 문제로 몇 시간 동안 고통을 겪었으며 이것이 당신이 그것을 고칠 수있는 방법입니다. 그러나 시작하기 전에이 솔루션에 몇 가지 조건이 있습니다.

모델 클래스

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

어댑터 클래스 생성자 및 모델 목록

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 [ onCheckChange 대신 onclick을 사용하십시오]

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

나에게 문제는 Done, Back 또는 외부 입력 터치로 EditText를 종료했을 때 발생했습니다. 이로 인해 입력 텍스트로 모델을 업데이트 한 다음 실시간 데이터 관찰을 통해 리사이클 러보기를 새로 고칩니다.

문제는 커서 / 포커스가 EditText에 남아 있다는 것입니다.

다음을 사용하여 초점을 삭제 한 경우 :

editText.clearFocus() 

리사이클 뷰의 데이터 변경 방법 알림이이 오류 발생을 중지했습니다.

이것이 이것이이 문제의 가능한 이유 / 해결책 중 하나라고 생각합니다. 이 예외는 완전히 다른 이유로 발생할 수 있으므로 다른 방식으로 수정 될 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.