-2

I have a recyclerview which its items contain textView and switchCompat. And in the same activity I have also a textView that have a numerical value in it. The task is when the switchCompat turned on the text view above the recyclerview which contain the numerical value should increase by the value in the recyclerview item textview. I already did that but when scrolling in the recyclerview the switchCompat back to the default state and the value of the numerical textview backs to its old value,

Any help with that?

I Apology for not being able to post a part of the code now and I'll do this as soon as i can, I just posted it now in case anyone pass through something like this before

Thank you

Dinith Rukshan Kumara
  • 638
  • 2
  • 10
  • 19

2 Answers2

0

The key to a recycler view or any adapter view in Android is to have the adapter adapt your models to the view. In your case your view is a TextView plus a Switch, so your adapter must adapt some model to this view. In this case I'd choose a simple model like this:

class ItemModel {
  String text;
  boolean on;
}

I've omitted getters and setters for simplicity

This model contains an string text which reflects the text in your text view and a boolean on that reflects the state of the switch. When true the switch is checked and when false it's unchecked.

There's tons of ways to represent this model. I've chosen this one, you may choose a different one. The point is, you need to save the state somewhere and this is what I mean by model - the view model.

Now let's build an adapter that can do 2 things - Update the models when the switch is clicked and tell the activity that the switch changed state. Here's one way to do this:

public class ItemsAdapter extends 
  RecyclerView.Adapter<ItemsAdapter.ViewHolder> {
    @NonNull
    private final List<ItemModel> itemModels;
    @Nullable
    private OnItemCheckedChangeListener onItemCheckedChangeListener;

    ItemsAdapter(@NonNull List<ItemModel> itemModels) {
        this.itemModels = itemModels;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
        ItemModel item = itemModels.get(position);
        holder.text.setText(item.text);
        holder.switchCompat.setChecked(item.on);

        // Make sure we update the model if the user taps the switch
        holder.switchCompat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                int adapterPosition = holder.getAdapterPosition();

                ItemModel tapped = itemModels.get(adapterPosition);
                itemModels.set(adapterPosition, new ItemModel(tapped.text, isChecked));

                if (onItemCheckedChangeListener != null) {
                    onItemCheckedChangeListener.onItemCheckedChanged(adapterPosition, isChecked);
                }
            }
        });
    }

    @Override
    public void onViewRecycled(@NonNull ViewHolder holder) {
        super.onViewRecycled(holder);

        holder.switchCompat.setOnCheckedChangeListener(null);
    }

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

    public void setOnItemCheckedChangeListener(@Nullable OnItemCheckedChangeListener onItemCheckedChangeListener) {
        this.onItemCheckedChangeListener = onItemCheckedChangeListener;
    }

    interface OnItemCheckedChangeListener {
        /**
         * Fired when the item check state is changed
         */
        void onItemCheckedChanged(int position, boolean isChecked);
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        TextView text;
        SwitchCompat switchCompat;

        ViewHolder(View itemView) {
            super(itemView);
            text = itemView.findViewById(R.id.item_text);
            switchCompat = itemView.findViewById(R.id.item_switch);
        }
    }
}

There's a lot to digest, but let's focus on the important bits - the method onBindViewHolder. The first 3 lines are the classic recycling of the view. We grab the model at the correct position and set the elements in the view that correspond to model's attributes.

Then it gets more interesting. We set a OnCheckedChangeListener to update the model and the activity every time the switch changes state. The first 3 lines change the model in the adapter and the rest uses the custom interface OnItemCheckedChangeListener to notify the listener about the switch change. It's important to notice that inside the method OnCheckedChangeListener you should no longer use position, but rather use holder.getAdapterPosition. This will give you the correct position in the adapter's data list.

Since now the adapter has always the correct models inside the data list, every time the method onBindViewHolder is called the adapter knows exactly how to setup the view. This means that while scrolling and recycling the views, it will preserve the state of each item within the models inside the data list.

It's important to remove the OnCheckedChangeListener when the view gets recycled - onViewRecycled. This avoids messing the count when the adapter is setting the value of switchCompat in the onBindViewHolder.

Here's an example of how the activity could look like:

public class MainActivity extends AppCompatActivity {
    private int count = 0;

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

        List<ItemModel> data = new ArrayList<>();

        for (int i = 1; i <= 100; i++)
            data.add(new ItemModel("Item " + i, false));

        ItemsAdapter adapter = new ItemsAdapter(data);

        ((RecyclerView) findViewById(R.id.recyclerview)).setAdapter(adapter);

        final TextView countTextView = findViewById(R.id.count);

        drawCount(countTextView);

        adapter.setOnItemCheckedChangeListener(new ItemsAdapter.OnItemCheckedChangeListener() {
            @Override
            public void onItemCheckedChanged(int position, boolean isChecked) {
                if (isChecked)
                    count++;
                else
                    count--;

                drawCount(countTextView);
            }
        });
    }

    private void drawCount(TextView countTextView) {
        countTextView.setText(String.valueOf(count));
    }
}

This code is meant to demonstrate the idea, not to follow :) In any case, we setup all the initial state and then set up the custom listener OnItemCheckedChangeListener to update the text view in the activity.

The layout files shouldn't be relevant here, but as you can imagine the activity has a text view with id count and there's a recycler view with the id recyclerview.

Hope this helps

Fred
  • 16,367
  • 6
  • 50
  • 65
0

It solved for me after adding the below method to the adapter:

@Override
public int getItemViewType(int position) {

    return position;
}
Kerelos
  • 83
  • 4