3

I'm adding radiogroups programatically to my recyclerview and it's working fine. But when I check it and scroll the recyclerview it loses checked radios.

I've seen many ways and examples of solutions, but I cannot achieve it. It's been some days in a row.

I'm saving the checked radio in model as you can see in code below.

Adapter:

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

    holder.priceGroup.removeAllViews();
    holder.priceGroup.setOnCheckedChangeListener(null);

    int id = (position+1)*100;
    checklistModel = mChecklists.get(position);
    holder.packageName.setText(checklistModel.getTitle());

    for(String price : checklistModel.getQuestions()){
        RadioButton rb = new RadioButton(NROptionLineAdapter.this.context);
        rb.setId(id++);
        rb.setText(price);
        holder.priceGroup.addView(rb);
    }
    holder.priceGroup.check(checklistModel.getSelectedId());

    holder.priceGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            checklistModel.setSelectedId(checkedId);
            Log.d(TAG, "onCheckedChanged: " + checkedId);
        }
    });

}

Holder

    OnNROptionListener onNROptionListener;

    public NROptionLineHolder(View itemView, OnNROptionListener onNROptionListener) {
        super(itemView);

        packageName = itemView.findViewById(R.id.package_name);
        priceGroup = itemView.findViewById(R.id.price_grp);

//        priceGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
//            @Override
//            public void onCheckedChanged(RadioGroup radioGroup, int i) {
//
//                Log.d(TAG, "onCheckedChanged: " + radioGroup.getCheckedRadioButtonId() + " " + i);
//            }
//        });

        this.onNROptionListener = onNROptionListener;
        itemView.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        onNROptionListener.onNROptionClick(getAdapterPosition());
    }

    public interface OnNROptionListener {
        void onNROptionClick(int position);
    }
}

EDIT 1 - Radio Group

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    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="wrap_content">
    <TextView
        android:id="@+id/package_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <RadioGroup
        android:id="@+id/price_grp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/package_name"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:orientation="horizontal"/>
</android.support.constraint.ConstraintLayout>

EDIT 2

As requested, here is the important code from my ChecklistActivity

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_checklist);

    intent = getIntent();
    size = intent.getIntExtra("size", 0);
    nr = intent.getIntExtra("nr", 0);

    Log.d(TAG, "Checklist Activity - Qtd Questões: " + size);
    Log.d(TAG, "Checklist Activity - NR: " + nr);

    btnSaveCheck = findViewById(R.id.btnSaveChecklist);

    mRecyclerView = findViewById(R.id.package_lst);
    setupRecycler();

    btnSaveCheck.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(getApplicationContext(), "Sucesso", Toast.LENGTH_SHORT).show();
        }
    });
}

private void setupRecycler() {

    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    mRecyclerView.setLayoutManager(layoutManager);

    setupList();

    mAdapter = new NROptionLineAdapter(data, this, getApplication());
    mRecyclerView.setAdapter(mAdapter);

}

private void setupList(){
    data = new ArrayList<>();

    class setupList extends AsyncTask<Void, Void, List<MRNrOption>> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected List<MRNrOption> doInBackground(Void... voids) {

            list = DatabaseClient
                    .getInstance(getApplicationContext())
                    .getAppDatabase()
                    .mrNrOptionDAO()
                    .loadAllByNRId(nr);
            return list;

        }

        @Override
        protected void onPostExecute(List<MRNrOption> list) {
            super.onPostExecute(list);

            List<String> priceList = new ArrayList<>();
            priceList.add("Sim");
            priceList.add("Não");
            priceList.add("Não se Aplica");

            for (int i=0; i<list.size(); i++) {
                Log.d(TAG, "NRs Activity - Adding To List: " + list.get(i).getTitle());
                data.add(new Checklist(
                        list.get(i).getTitle(),
                        priceList)
                );

                mAdapter.notifyDataSetChanged();
            }

        }
    }

    setupList lm = new setupList();
    lm.execute();
}

RecyclerView with RadioButtons

EDIT 3 - Important

The RadioGroups and RadioButtons are programatically generated because I'm getting all questions from server, the number of questions are different depending on previous selections made by user, that's why I need it this way.

EDIT 4

GIF to enhance the problem visualization

EDIT 5 - Checklist Model Class

    public class Checklist {

    String title;
    List<String> questions;
    boolean isRadioButtonAdded;
    int selectedId;

    public Checklist(String title, List<String> questions) {
        this.title = title;
        this.questions = questions;
    }

    public Checklist(){}

    public boolean getIsAdded(){
        return isRadioButtonAdded;
    }

    public void setIsAdded(boolean isAdded){
        this.isRadioButtonAdded = isAdded;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public List<String> getQuestions() {
        return questions;
    }

    public void setQuestions(List<String> questions) {
        this.questions = questions;
    }

    public int getSelectedId() {
        return selectedId;
    }

    public void setSelectedId(int selectedId) {
        this.selectedId = selectedId;
    }
}
Alan Godoi
  • 657
  • 1
  • 12
  • 39

6 Answers6

1

Though i'm not sure as to whether this will solve your problem or not, but as an optimiziation also a good practice you should attach listeners to the onCreateViewHolder instead of the onBindViewHolder this prevents multiple objects from getting created for the listener.

Why dont you move this code inside the onCreateViewHolder

this block inside the view holder you have:

priceGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            checklistModel.setSelectedId(checkedId);
            Log.d(TAG, "onCheckedChanged: " + checkedId);
        }
    });
MadScientist
  • 2,134
  • 14
  • 27
  • @Alan could you try logging the values for your generated `id` and the `selectedId` maybe the algorithm you have is generating wrong values, you if so you can try using `holder.adapterPosition` – MadScientist Mar 05 '19 at 13:03
  • The values generated for id and selectedId are as expected. – Alan Godoi Mar 05 '19 at 13:06
1

Try moving your setOnCheckedChangeListener code to ViewHolder and update your mCheckList here:


priceGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
        mCheckList.get(getAdapterPosition()).setSelectedId(checkedId);
        Log.d(TAG, "onCheckedChanged: " + radioGroup.getCheckedRadioButtonId() + " " + i);
}});

Main problem is that you're not updating correct items state. When you click on radio button it will update only last item onBindViewHolder called because checklistModel holds only last reference. To fix this you always need to access mainList inside listeners.

IgorGanapolsky
  • 26,189
  • 23
  • 116
  • 147
Vygintas B
  • 1,624
  • 13
  • 31
0

Save the checked / unchecked status of the radio button to your model (i.e. your items in the list should have a field for this) when the onClick event happens. When you bind the ViewHolder, make sure you set checkbox's value to whatever you saved in your model.

eOlcer
  • 75
  • 1
  • 5
0

The unique working solution was to set RecyclerView to not recycleable in ViewHolder.

this.setIsRecyclable(false);
Alan Godoi
  • 657
  • 1
  • 12
  • 39
-1

How about the following code?

Adapter:

Since the view is recycled, I thought that the unique id is a mistake.

int id = (position+1)*100;

to

int id = 1;
ysys
  • 82
  • 4
  • There's no difference, the same thing happens. – Alan Godoi Mar 02 '19 at 17:32
  • How will it change if you comment out this code in addition to the above code? **Adapter:** `//holder.priceGroup.removeAllViews();` However, the ID is the resource ID. Since it may overlap with resource IDs other than radio buttons, I think that it is better to define radio buttons in XML as well. – ysys Mar 03 '19 at 00:06
  • It's not possible to define radio buttons in XML because it depends on number of questions, if I have 3 questions from server, I need 3 RadioGroups with 3 RadioButtons inside. If the number of questions change, the number of RadioGroups change too. By removing 'removeAllViews()', it keeps creating radiobuttons like six per question. – Alan Godoi Mar 04 '19 at 11:11
  • I understand that the number of radio buttons needs to be dynamic. What will be the result of placing the code below outside the for statement? **Adapter:** `holder.priceGroup.check(checklistModel.getSelectedId());` – ysys Mar 04 '19 at 13:34
  • It's already outside for statement, I'll update the question and I'll try to add a gif to help visualization. – Alan Godoi Mar 04 '19 at 13:57
-1

The way recyclerview works is that when you scroll down or up until the view is invisible, it will store the state but when you scroll down recyclerview or scroll up recyclerview it will destroy the row and it will put a new row into that position.so the entire row's view will be destroyed.

The solution to the question is that you add another variable to the list you are using and when the radio button changes state you store the data and move on. like this in your data model class And in Model Class you define which list is recyclerview is using.

Boolean is stateclicked;
int state position;

and in OnBindViewholder you can get the value of this data. If the value is null then it's not clicked and if it is clicked then you change the boolean to yes and put the state's value into the integer

raj kavadia
  • 926
  • 1
  • 10
  • 30