android – Selecting one RadioButton value and scrolling back removing the selected one in RecyclerView-ThrowExceptions

Exception or error:

In my application am displaying 20 multiple choice questions with the help of RecyclerView.

If I change the value of first RadioGroup and scrolls down, again scrolls up removing the selected value in RecycelarView and also i want to use that selected RadioButton value further, I was also checked link1 but i did’t understood what he is doing.

Here is my sample code snippet let me know if you need any clarification.

    package com.testing.survey;

import java.util.List;

import android.annotation.SuppressLint;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.TextView;

@SuppressWarnings("rawtypes")
public class DataAdapter extends RecyclerView.Adapter<DataAdapter.ViewHolder> {

    private List<Student> stList;

    public DataAdapter(List<Student> students) {
        this.stList = students;
    }

    // Create new views
    @Override
    public DataAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
            int viewType) {
        // create a new view
        View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.list_row, null);

        // create ViewHolder

        ViewHolder viewHolder = new ViewHolder(itemLayoutView);

        return viewHolder;
    }

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

        final int pos = position;

        viewHolder.tvQuestionNumber.setText(stList.get(position).getQuestionNumber());

        viewHolder.tvQuestion.setText(stList.get(position).getQuestion());

        viewHolder.rbAns1.setText(stList.get(position).getAnswer1());

        viewHolder.rbAns2.setText(stList.get(position).getAnswer2());

        viewHolder.rbAns3.setText(stList.get(position).getAnswer3());

        viewHolder.rbAns4.setText(stList.get(position).getAnswer4());

        viewHolder.rbAns5.setText(stList.get(position).getAnswer4());

        //viewHolder.rgAnswers.clearCheck();

        viewHolder.rgAnswers.check(stList.get(position).getSelectedRadioButtonId());
        viewHolder.rgAnswers.setTag(new Integer(position));


        Log.v("select"+position,stList.get(position).getSelectedRadioButtonId()+"");

        viewHolder.rgAnswers.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                // TODO Auto-generated method stub
                int radioButtonID = group.getCheckedRadioButtonId();
                View radioButton = group.findViewById(radioButtonID);
                int clickedPos = ((Integer)group.getTag()).intValue(); 

                //Student contact=(Student)group.getTag();

                //contact.setSelectedRadioButtonId(radioButtonID);
                stList.get(clickedPos).setSelectedRadioButtonId(radioButtonID);


                Log.v("hello"+clickedPos,stList.get(clickedPos).getSelectedRadioButtonId()+"");
            }
        });

    }

    // Return the size arraylist
    @Override
    public int getItemCount() {
        return stList.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public TextView tvQuestionNumber;
        public TextView tvQuestion;

        public RadioGroup rgAnswers;
        public RadioButton rbAns1,rbAns2,rbAns3,rbAns4,rbAns5;

        public Student singlestudent;

        public ViewHolder(View itemLayoutView) {
            super(itemLayoutView);

            tvQuestionNumber = (TextView) itemLayoutView.findViewById(R.id.tvQuestionNumber);

            tvQuestion = (TextView) itemLayoutView.findViewById(R.id.tvQuestion);
            rgAnswers=(RadioGroup)itemLayoutView.findViewById(R.id.rgAnswers);
            rbAns1=(RadioButton)itemLayoutView.findViewById(R.id.rbAnswer1);
            rbAns2=(RadioButton)itemLayoutView.findViewById(R.id.rbAnswer2);
            rbAns3=(RadioButton)itemLayoutView.findViewById(R.id.rbAnswer3);
            rbAns4=(RadioButton)itemLayoutView.findViewById(R.id.rbAnswer4);
            rbAns5=(RadioButton)itemLayoutView.findViewById(R.id.rbAnswer5);

        }

    }

    // method to access in activity after updating selection
    public List<Student> getStudentist() {
        return stList;
    }

}
How to solve:

I created a sample code that will work as per your req.

First we have to create the xml as follows :

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerViewAppList"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</LinearLayout>

Then we will create an interface as follows

public interface OnOptionSelected {
public void onOptionSelected(int position,int itemSelected);
}

Then we will create the model class as follows :

public class QuestionModel {

private String question;
private int seleectedAnswerPosition;
private boolean op1Sel,op2Sel,op3Sel; // options 

public boolean isOp1Sel() {
    return op1Sel;
   }

public void setOp1Sel(boolean op1Sel) {
    this.op1Sel = op1Sel;
    if(op1Sel){ // To make sure only one option is selected at a time
        setOp2Sel(false);
        setOp3Sel(false);
    }
   }

    public boolean isOp2Sel() {
      return op2Sel;
   }

   public void setOp2Sel(boolean op2Sel) {
    this.op2Sel = op2Sel;
    if(op2Sel){
        setOp1Sel(false);
        setOp3Sel(false);
     }
      }

 public boolean isOp3Sel() {
    return op3Sel;
     }

 public void setOp3Sel(boolean op3Sel) {
    this.op3Sel = op3Sel;
    if(op3Sel){
        setOp2Sel(false);
        setOp1Sel(false);
     }
    }

    public int getSeleectedAnswerPosition() {
    return seleectedAnswerPosition;
    }

  public void setSeleectedAnswerPosition(int seleectedAnswerPosition) {
    this.seleectedAnswerPosition = seleectedAnswerPosition;
   }

  public String getQuestion() {
    return question;
  }
 public void setQuestion(String question) {
    this.question = question;
  }
     } 

Then we will create the view for row :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/question"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

    <RadioButton
        android:id="@+id/radoptionOne"
        android:text="May Be"
        android:checked="false"
        android:saveEnabled="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <RadioButton
        android:checked="false"
        android:id="@+id/radoptionTwo"
        android:text="NO"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <RadioButton
        android:checked="false"
        android:id="@+id/radoptionThree"
        android:text="Yes"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
   </LinearLayout>

Adapter class as follows:

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

   private List<QuestionModel> questionModels;


public void setOnOptionSelected(OnOptionSelected onOptionSelected) {
    this.onOptionSelected = onOptionSelected;
}

private OnOptionSelected onOptionSelected;


public List<QuestionModel> getQuestionModels() {
    return questionModels;
}

public void setQuestionModels(List<QuestionModel> questionModels) {
    this.questionModels = questionModels;
}

public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    TextView question;
    RadioGroup radioGroup;
    RadioButton op1, op2, op3;

    ViewHolder(View view) {
        super(view);
        question = (TextView) view.findViewById(R.id.question);
        //radioGroup=(RadioGroup)view.findViewById(R.id.radGroup);
        op1 = (RadioButton) view.findViewById(R.id.radoptionOne);
        op2 = (RadioButton) view.findViewById(R.id.radoptionTwo);
        op3 = (RadioButton) view.findViewById(R.id.radoptionThree);
        op1.setOnClickListener(this);
        op2.setOnClickListener(this);
        op3.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.radoptionOne:
                onOptionSelected.onOptionSelected(getAdapterPosition(), 1);
                break;

            case R.id.radoptionTwo:
                onOptionSelected.onOptionSelected(getAdapterPosition(), 2);
                break;

            case R.id.radoptionThree:
                onOptionSelected.onOptionSelected(getAdapterPosition(), 3);
                break;
        }
    }
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v;
    // create a normal view
    v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.row_view, parent, false);
    return new ViewHolder(v);

}

@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
    viewHolder.question.setText(questionModels.get(position).getQuestion());

    Log.e("POSITION" + position, "1" + questionModels.get(position).isOp1Sel());

    Log.e("POSITION" + position, "2" + questionModels.get(position).isOp2Sel());
    Log.e("POSITION" + position, "3" + questionModels.get(position).isOp3Sel());


    viewHolder.op1.setChecked(questionModels.get(position).isOp1Sel());
    viewHolder.op2.setChecked(questionModels.get(position).isOp2Sel());
    viewHolder.op3.setChecked(questionModels.get(position).isOp3Sel());


}

@Override
public int getItemCount() {
    if (questionModels != null) {
        return questionModels.size();
    }
    return 0;
}
}

Then the Activity class :

public class MainActivity extends Activity implements OnOptionSelected{

private RecyclerView mRecyclerView;
private List<QuestionModel> questionModels;
private  QuestionAdapter questionAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mRecyclerView =(RecyclerView)findViewById(R.id.recyclerViewAppList);
    mRecyclerView.setHasFixedSize(true);
    questionModels=new ArrayList<QuestionModel>();
    for (int i=0;i<20;i++)
    {
        QuestionModel questionModel=new QuestionModel();
        questionModel.setQuestion("Question " + (i + 1));
        questionModels.add(questionModel);
    }
    questionAdapter =new QuestionAdapter();
    questionAdapter.setOnOptionSelected(this);
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    questionAdapter.setQuestionModels(questionModels);
    mRecyclerView.setLayoutManager(layoutManager);
    mRecyclerView.setAdapter(questionAdapter);
}


@Override
public void onOptionSelected(int position, int itemSelected) {
    questionModels.get(position).setSeleectedAnswerPosition(itemSelected);
    switch (itemSelected){
        case 1:
            questionModels.get(position).setOp1Sel(true);
            break;

        case 2:
            questionModels.get(position).setOp2Sel(true);
            break;
        case 3:
            ((QuestionModel)questionModels.get(position)).setOp3Sel(true);
            break;
    }
    questionAdapter.setQuestionModels(questionModels);
    questionAdapter.notifyDataSetChanged();
   // mRecyclerView.setAdapter(questionAdapter);

}
 }

I tried the solution with Radiogroup but it was not working properly( sometime it was retaining the old value). Then I changed the way of the selecting options in my model class.

sample

###

Modify your onBindViewHolder() to this

public void onBindViewHolder(ViewHolder viewHolder, int position) {

        final int pos = position;

        viewHolder.tvQuestionNumber.setText(stList.get(position).getQuestionNumber() + "");

        viewHolder.tvQuestion.setText(stList.get(position).getQuestion());

        viewHolder.rbAns1.setText(stList.get(position).getAnswer1());

        viewHolder.rbAns2.setText(stList.get(position).getAnswer2());

        viewHolder.rbAns3.setText(stList.get(position).getAnswer3());

        viewHolder.rbAns4.setText(stList.get(position).getAnswer4());

        viewHolder.rbAns5.setText(stList.get(position).getAnswer5());

        //viewHolder.rgAnswers.clearCheck();

        viewHolder.rgAnswers.setTag(position);


        Log.v("select" + position, stList.get(position).getSelectedRadioButtonId() + "");

        viewHolder.rgAnswers.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                // TODO Auto-generated method stub
                int radioButtonID = group.getCheckedRadioButtonId();
                View radioButton = group.findViewById(radioButtonID);
                int clickedPos = (Integer) group.getTag();


               stList.get(clickedPos).setSelectedRadioButtonId(radioButtonID);

               // if you want to get selected button's info such as tag, text... etc.
                RadioButton radioButton = (RadioButton) viewHolder.itemView.findViewById(radioButtonID);

                if(radioButton != null) {
                    String customTag = radioButton.getTag().toString();
                    stList.get(clickedPos).setCustomTag(customTag);
                }


                Log.v("hello" + clickedPos, stList.get(clickedPos).getSelectedRadioButtonId() + "");
            }
        });

        viewHolder.rgAnswers.check(stList.get(position).getSelectedRadioButtonId());

    }

It should work. If any issues please let me know.

###

This problem occurs because of a bug in either RecyclerView or somewhere in the Android SDK. Anyway, I took the advice from another SO question – onCheckedChanged called automatically.

For RadioGroup, it will be a little different. There are two ways to go about. I recommend 1st one since it is plug-n-play.

  1. set OnCheckedChangeListener on RadioGroup:

    mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            if(checkedId == -1) {
                Log.v("onCheck", "Android bug since RadioButton doesn't get unchecked normally!");
            }
            else {
                Log.v("onCheck", "Valid click. By user");
                mMyListObjectArr[position].setChecked(checkedId);
            }
        }
    });
    
  2. or set onCheckedChangeListener on all RadioButtons inside the RadioGroup:

    CompoundButton.OnCheckedChangeListener onCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if(buttonView.isPressed()) {
                Log.v("onCheck", position + ", valid click by user");
                mMyListObjectArr[position].setChecked(buttonView.getId());
            }
            else {
                Log.v("onCheck", "Android bug");
            }
        }
    };
    mRadioButton1.setOnCheckedChangeListener(onCheckedChangeListener);
    mRadioButton2.setOnCheckedChangeListener(onCheckedChangeListener);
    

###

You can do it in this way…..

inside your bean class add one field say

String final_answer;
public String getFinalAnswer(){
  return final_answer;
}

public void setFinalAnswer(String final_answer){
  this.final_answer = final_answer;
}

in adapter…..

public void onBindViewHolder(final ViewHolder viewHolder, final int position) {

    final Student student = stList.get(position);

    viewHolder.tvQuestionNumber.setText(student.getQuestionNumber());

    viewHolder.tvQuestion.setText(student.getQuestion());

    viewHolder.rbAns1.setText(student.getAnswer1());

    viewHolder.rbAns2.setText(student.getAnswer2());

    viewHolder.rbAns3.setText(student.getAnswer3());

    viewHolder.rbAns4.setText(student.getAnswer4());

    viewHolder.rbAns5.setText(student.getAnswer4());

    viewHolder.rgAnswers.clearCheck();

   if(student.getSelectedRadioButtonId()!=null)
     viewHolder.rgAnswers.check(student.getSelectedRadioButtonId());
   else
     viewHolder.rgAnswers.clearCheck();

    viewHolder.rgAnswers.setOnCheckedChangeListener(new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
          if(checkedId != -1){
            student.setSelectedRadioButtonId(checkedId);
            RadioButton rb = (RadioButton)viewHolder.rgAnswers.
                                  findViewById(checkedId);

            student.setFinalAnswer(rb.getText);
            stList.get(position) = student;
          }
        }
    });
}

To get selected answers finally…write this code in main activity

  public void getSelectedAnswers(){

       for(int i=0;i<slist.length;i++){
          //get selected answer
          Log.i("answer",slist.get(i).getFinalAnswer());
       }
    }

###

The problem is that the recycler view is recycling (like the name says) your views.

The system creates a certain amount of ViewHolders to fill your screen after reaching that amount it reuses the already existent ViewHolders.

If you set the button checked in your first listentry and it reuses the ViewHolder for your 7th listentry the button is still checked(because it got set in the ViewHolder).

To fix this problem you need to set the default appearance for your listentries in onBindViewHOlder every time.

Update:

public void onBindViewHolder(final ViewHolder viewHolder, final int position) {

    final Student student = stList.get(position);

    viewHolder.tvQuestionNumber.setText(student.getQuestionNumber());

    viewHolder.tvQuestion.setText(student.getQuestion());

    viewHolder.rbAns1.setText(student.getAnswer1());

    viewHolder.rbAns2.setText(student.getAnswer2());

    viewHolder.rbAns3.setText(student.getAnswer3());

    viewHolder.rbAns4.setText(student.getAnswer4());

    viewHolder.rbAns5.setText(student.getAnswer4());

    viewHolder.rgAnswers.clearCheck();

    viewHolder.rgAnswers.check(student.getSelectedRadioButtonId());

    viewHolder.rgAnswers.setOnCheckedChangeListener(new OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            student.setSelectedRadioButtonId(checkedId);
            Log.v("hello"+position,checkedId+"");
        }
    });
}

I slightly updated your new approach, maybe this helps ?

###

As i have seen your code…

just add single line

viewHolder.rgAnswers.setOnCheckedChangeListener(null);

in onBindViewHolder() method above of this line

viewHolder.rgAnswers.check(stList.get(position).getSelectedRadioButtonId());

if it solved your problem ..let me know …
i will explain you a scenario why its happening .. Because i faced similer problem and solved using this solution.

###

In your model class add

private int checkedId = -1;

public int getCheckedId() {
    return checkedId;
}

public void setCheckedId(int checkedId) {
    this.checkedId = checkedId;
}

and change onBindViewHolder in adapter like this:

 @Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
    viewHolder.tvQuestion.setText(stList.get(position).getQuestion());

    viewHolder.rbAns1.setText(stList.get(position).getAnswer1());
    viewHolder.rbAns2.setText(stList.get(position).getAnswer2());
    viewHolder.rbAns3.setText(stList.get(position).getAnswer3());
    viewHolder.rbAns4.setText(stList.get(position).getAnswer4());
    viewHolder.rbAns5.setText(stList.get(position).getAnswer5());

    viewHolder.radGrp.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {

        @Override
        public void onCheckedChanged(RadioGroup rgp, int checkedId) {
            // TODO Auto-generated method stub
            stList.get(position).setCheckedId(rgp.getCheckedRadioButtonId());
        }
    });

    viewHolder.radGrp.check(stList.get(position).getCheckedId());
}

###

Inside your student class add a boolean variable isSelected. Now on checkedChangeListener of each radiogroup, change the value of isSelected to mark whether some value has been selected or no selection has been made. Now inside onBindViewHolder() just do

    if(stList.get(position).isSelected){
  viewHolder.rgAnswers.check(stList.get(position).getSelectedRadioButtonId());
    }else{
    viewHolder.rgAnswers.clearCheck();
    }

At the end you can iterate over the whole list and check for which objects, isSelected is true

###

RecyclerView came over ListView and its major property that is It Reuses cells while scrolling up and down.
Due to this it do not flick while scrolling even if you have lots of data in your list.
You are getting problem due to Reuse of cells in RecyclerView.

You can overcome to this problem by binding your RecyclerView list data to Modal class with getter and setter method.
You can display checkbox checked or unchecked on basis of cell position and corresponding data.
In my sample i used setOnClickListener instead of setOnCheckedChangeListenerto to show checkbox checked or unchecked.

Leave a Reply

Your email address will not be published. Required fields are marked *