13

My recyclerview is duplicating items when I roll it and I'm already calling adapter.notifyDataSetChanged().

So, probably I'm calling data set update at wrong place, but I can't find how it works.

Here is some code:

       RecyclerView packageRecyclerView;
        Intent intent;
        ChecklistAdapter recyclerViewAdapter;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_checklist);
    
            intent = getIntent();
            size = intent.getIntExtra("size", 0);
            Log.d(TAG, "onCreate - Qtd Questões: " + size);
    
            packageRecyclerView = findViewById(R.id.package_lst);
    
            LinearLayoutManager recyclerLayoutManager = new LinearLayoutManager(this);
            packageRecyclerView.setLayoutManager(recyclerLayoutManager);
    
            DividerItemDecoration dividerItemDecoration =
                    new DividerItemDecoration(packageRecyclerView.getContext(),
                            recyclerLayoutManager.getOrientation());
            packageRecyclerView.addItemDecoration(dividerItemDecoration);
    
            List<Checklist> modelList = new ArrayList<>();
            recyclerViewAdapter = new ChecklistAdapter(modelList,this);
            modelList = getPackages();
            recyclerViewAdapter = new ChecklistAdapter(modelList,this);
    
    //        recyclerViewAdapter = new ChecklistAdapter(getPackages(),this);
    
            packageRecyclerView.setAdapter(recyclerViewAdapter);
    
    
        }
    
        private List<Checklist> getPackages() {
            List<Checklist> modelList = new ArrayList<>();
            Log.d(TAG, "getPackages: " + size);
            for (int i=0; i<size;i++) {
    
                List<String> priceList = new ArrayList<>();
                priceList.add("Sim");
                priceList.add("Não");
                priceList.add("Não se Aplica");
                modelList.add(new Checklist(intent.getStringExtra("q"+i), priceList));
            }
    
            recyclerViewAdapter.notifyDataSetChanged();
            return modelList;
        }

Here is my adapter:

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

    private List<Checklist> packageList;
    private Context context;

    public ChecklistAdapter(List<Checklist> packageListIn
            , Context ctx) {
        packageList = packageListIn;
        context = ctx;
    }

    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

    @Override
    public void setHasStableIds(boolean hasStableIds) {
        super.setHasStableIds(hasStableIds);
    }

    @Override
    public long getItemId(int position) {
        return super.getItemId(position);
    }

    @Override
    public ChecklistAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                                    int viewType) {

        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.rv_checklistlines, parent, false);

        ChecklistAdapter.ViewHolder viewHolder =
                new ChecklistAdapter.ViewHolder(view);
        return viewHolder;
    }


    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
        super.onBindViewHolder(holder, position, payloads);
    }

    @Override
    public void onBindViewHolder(ChecklistAdapter.ViewHolder holder,
                                 int position) {
        Checklist packageModel = packageList.get(position);
        holder.packageName.setText(packageModel.getTitle());

        int id = (position+1)*100;
        for(String price : packageModel.getQuestions()){
            RadioButton rb = new RadioButton(ChecklistAdapter.this.context);
            rb.setId(id++);
            rb.setText(price);

            holder.priceGroup.addView(rb);
        }
    }

    @Override
    public int getItemCount() {
        return packageList.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView packageName;
        public RadioGroup priceGroup;

        public ViewHolder(View view) {
            super(view);
            packageName = view.findViewById(R.id.package_name);
            priceGroup = view.findViewById(R.id.price_grp);

            priceGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(RadioGroup radioGroup, int i) {

                    Toast.makeText(ChecklistAdapter.this.context,
                            "Radio button clicked " + radioGroup.getCheckedRadioButtonId(),
                            Toast.LENGTH_SHORT).show();
                    
                }
            });
        }
    }
}
    

In the image below you can see that radio buttons are duplicated and there is some extra blank space in all rows. I saw many questions here and in another forums about this, but it's not clear to me where to call this update dataset.

enter image description here

I'm not an advanced Android developer, so if you can explain, it will be easier to me.

EDIT 1

Checklist class:

       import java.util.List;
    
    public class Checklist {
    
        String title;
        List<String> questions;
    
        public Checklist(String title, List<String> questions) {
            this.title = title;
            this.questions = questions;
        }
    
        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;
}

EDIT 2

Adapter Code updated

Community
  • 1
  • 1
Alan Godoi
  • 657
  • 1
  • 12
  • 39

10 Answers10

14

Use holder.setIsRecyclable(false) in onCreateViewHolder()

It worked for me.

Marcel Bro
  • 4,907
  • 4
  • 43
  • 70
Avirup Ghosh
  • 171
  • 1
  • 5
13

I replicated your problem.

adding holder.priceGroup.removeAllViews(); on onBindViewHolder will fix it. Like so:

 @Override
public void onBindViewHolder(ChecklistAdapter.ViewHolder holder,
                             int position) {
    Checklist packageModel = packageList.get(position);
    holder.packageName.setText(packageModel.getTitle());

    int id = (position+1)*100;
    holder.priceGroup.removeAllViews();

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

You were adding to the view every time without removing the previous views.

COYG
  • 1,538
  • 1
  • 16
  • 31
  • 1
    Awesome. It works. I also removed setHasStableIds and getItemViewType, maybe it can help someone else. Thx all for your time. – Alan Godoi Feb 22 '19 at 05:19
  • No bother, I didn't even include them when I copied your code, just used the original. – COYG Feb 22 '19 at 05:20
  • 1
    Just realized one more thing, it do not duplicates anymore, but it loses selected radios when I scroll. – Alan Godoi Feb 22 '19 at 05:23
  • radios you clicked say on question 3? and you scroll down to say question 25, and then return to question 3? and it is unchecked? Is that the problem? – COYG Feb 22 '19 at 05:26
  • Yes, thats the problem. – Alan Godoi Feb 22 '19 at 05:26
  • 1
    i understand actually, the views get destroyed every time you scroll, you will need to store this data and retrieve it when ever you return and set it when you return to the view. – COYG Feb 22 '19 at 05:28
  • 1
    See the answers [here](https://stackoverflow.com/questions/32255720/recyclerview-adapter-resets-radio-buttons) – COYG Feb 22 '19 at 05:30
  • 1
    One more time helped me. – Alan Godoi Feb 22 '19 at 05:32
  • 1
    THANK YOU THANK YOU THANK YOU!!!!. I have similar problem, because i was adding views to parent as recyclerview. The data got duplicated after adding new data / scrolling, now I know why, thanks!!!! – Irfandi D. Vendy Apr 30 '20 at 06:23
6

You just need to call list.clear(); before insert into RecyclerViewAdapter. then call after call adapter.notifyDatasetChange()

Gk Mohammad Emon
  • 6,084
  • 3
  • 42
  • 42
Paras Bhatt
  • 61
  • 1
  • 2
4

you have to setHasStableIds(true) to your Adapter in your activity.

and in your Adapter class you have to set below method. It may help you. it is work for me.

  @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }


    @Override
    public void setHasStableIds(boolean hasStableIds) {
        super.setHasStableIds(hasStableIds);
    }

you want like this?

enter image description here

Vasudev Vyas
  • 726
  • 1
  • 10
  • 28
4

Put holder.setIsRecyclable(false) in onBindViewHolder() like below example.

@Override
    public void onBindViewHolder(UsageStatVH holder, int position) {
        holder.bindTo(list.get(position));
        holder.setIsRecyclable(false);
    }
donmj
  • 379
  • 7
  • 13
1

Please override the below method in ChecklistAdapter

@Override
public long getItemId(int position) {
    return position;
}

And in in your onCreate() add:

recyclerViewAdapter.setHasStableIds(true);
Zain
  • 37,492
  • 7
  • 60
  • 84
0

It is because you are setting your list three time to adapter,

At this time i can't write whole code, but replace this block in your code it will work.

Try this code,

    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(packageRecyclerView.getContext(), recyclerLayoutManager.getOrientation()); 
    packageRecyclerView.addItemDecoration(dividerItemDecoration);
    List<Checklist> modelList = new ArrayList<>(); 
    modelList = getPackages(); recyclerViewAdapter = new ChecklistAdapter(modelList,this);  
    packageRecyclerView.setAdapter(recyclerViewAdapter);

And also remove below line from your getPackages() function.

recyclerViewAdapter.notifyDataSetChanged();
John Joe
  • 12,412
  • 16
  • 70
  • 135
Prayag Gediya
  • 1,078
  • 2
  • 11
  • 20
0

Instead of

 List<Checklist> modelList = new ArrayList<>();
    recyclerViewAdapter = new ChecklistAdapter(modelList,this);
    modelList = getPackages();
    recyclerViewAdapter = new ChecklistAdapter(modelList,this);
    packageRecyclerView.setAdapter(recyclerViewAdapter);

Use like this

    recyclerViewAdapter = new ChecklistAdapter(getPackages(),this);
    packageRecyclerView.setAdapter(recyclerViewAdapter);

AND modify your getPackages method like

private List<Checklist> getPackages() {
 List<Checklist> modelList = new ArrayList<>();
 Log.d(TAG, "getPackages: " + size);

for (int i=0; i<size;i++) {
   List<String> priceList = new ArrayList<>();
    priceList.add("Sim");
    priceList.add("Não");
    priceList.add("Não se Aplica");
    modelList.add(new Checklist(intent.getStringExtra("q"+i), priceList));
}
return modelList;
}

Hope this will help you.

Jakir Hossain
  • 3,830
  • 1
  • 15
  • 29
0

Modify your Checklist...

public class Checklist {

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

    public Checklist(String title, List<String> questions) {
        this.title = title;
        this.questions = questions;
    }
    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;
}

And modify your onBindViewHolder

@Override
public void onBindViewHolder(ChecklistAdapter.ViewHolder holder,
                             int position) {
    Checklist packageModel = packageList.get(position);
    holder.packageName.setText(packageModel.getTitle());

    int id = (position+1)*100;
    if(!packageModel.getIsAdded){
        for(String price : packageModel.getQuestions()){
            RadioButton rb = new RadioButton(ChecklistAdapter.this.context);
            rb.setId(id++);
            rb.setText(price);

            holder.priceGroup.addView(rb);
            packageModel.setIsAdded(true)
        }
    }
}
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Jakir Hossain
  • 3,830
  • 1
  • 15
  • 29
0

If you're working with DiffUtils I had same problem making duplicate item while i tried to get list from Room DB then updating Recyclerview

before

private fun getLocalMessage(offset: Int, scrollToEnd: Boolean) {
    vm.getLocalMessage(channelId = channelId, offset = offset) {
        lifecycleScope.launch {
            val job = CoroutineScope(Dispatchers.IO).launch {
                channelMessageDB.addAll(it)
            }
            job.join()
            bindMessagesToRv(messageList = channelMessageDB, enableScrollToEnd = scrollToEnd)
        }
    }
}

private fun bindMessagesToRv(
    message: ChannelMessageEntity.ChannelMessageData? = null,
    messageList: List<ChannelMessageEntity.ChannelMessageData> = emptyList(),
    enableScrollToEnd: Boolean = false
) {
    lifecycleScope.launch {
        if (messageList.isNotEmpty()) {
            val job = CoroutineScope(Dispatchers.IO).launch {
                channelMessage.addAll(messageList)
            }
            job.join()
            channelMessage.sortedBy {
                it.messageTime
            }.apply {
                adapter.addItem(newItem = this)
            }

        }
        message?.let {
            channelMessage.add(it)
            adapter.addItem(newItem = channelMessage)

        }

    }
}

Then I update my localmessage function to this

private fun getLocalMessage(offset: Int, scrollToEnd: Boolean) {
    vm.getLocalMessage(channelId = channelId, offset = offset) {
        lifecycleScope.launch {
            bindMessagesToRv(messageList = it, enableScrollToEnd = scrollToEnd)
        }
    }
}

already I had two list which I pass them , now just one . Might help , GL :)

mistelo
  • 68
  • 7