Matt Strom
Matt Strom

Reputation: 720

Event on RecyclerView items affects multiple rows at a time instead of one specific row

To summarize my app fragment, I have a RecyclerView who's size is dynamic, and relative to a ViewModel variable (when this variable changes, RecyclerView's list of items grows or shrinks respectively).

Each row has a couple buttons. Each of those buttons has a OnClickListener on it. (When clicked, the button disappears).

Problem: When a button is clicked, not only that button disappears, but every additional multiple of 7 (index-wise) disappears as well.

For example: You press a button in row 1, and the exact same thing happens in row 8, 15, and 22, even though you only clicked row 1.

I am currently passing a listener through my fragment's onViewCreated method to a private listener variable in the adapter (listener's set in the constructor).

I'll show the relavent bits of my Fragment and Adapter:

Fragment:

public class MyFragment extends Fragment {
    private MyModel model;
    private ArrayList<Data> dataList;
    private int numberOfDataItems;

    private RecyclerView content;
    private RecycleAdapter adapter;

    private View.OnClickListener onItemClickLIstener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            RecyclerAdapter.ViewHolder viewHolder = (RecyclerAdapter.ViewHolder) view.getTag();

            // This is the buggy bit
            viewHolder.myButton.setVisibility(View.GONE);

            int position = viewHolder.getAdapterPosition();
            Data data = dataList.get(position);
            // TODO: eventually do some logic here with the data
        }
    }

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        content = view.findViewById(R.id.content);
        model.createDataList(numberOfDataItems);
        dataList = model.getDataList();

        adapter = new RecycleAdapter(dataList);
        content.setAdapter(adapter);
        content.setLayoutManager(new LinearLayoutManager(getContext()));

        adapter.setOnItemClickListener(onItemClickListener);
    }
}

Adapter:

public class RecycleAdapter extends RecyclerView.Adapter<RecycleAdapter.ViewHolder> {
    private List<Data> dataList;
    private View.OnClickListener onItemClickListener;

    public class ViewHolder extends RecyclerView.ViewHolder {
        public Button myButton;

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

            myButton = itemView.findViewById(R.id.myButton);
            myButton.setTag(this);
            myButton.setOnClickListener(onItemClickListener);
        }
    }

    @Override
    public RecycleAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context conext = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        View DataView = inflater.inflate(R.layout.data_layout, parent, false);
        return new ViewHolder(dataView);
    }

    @Override
    public void onBindViewHolder(@NonNull RecycleAdapter.ViewHolder viewHolder, int position) {
        Data data = dataList.get(position);
        // Do stuff with data

        myButton.setText(data);
    }

    public void setOnItemClickListener(View.OnClickListener itemClickListener) {
        onItemClickListener = itemClickListener;
    }
}

Upvotes: 1

Views: 2078

Answers (2)

Ferran
Ferran

Reputation: 1448

You are passing to your onClick listener the viewHolder while doing that

   myButton.setTag(this);  // this = viewHolder

The viewHolder is a recyclable view. So, you cannot treat this view as a unique item view. Remember that a viewHolder is created to be reused by other items. That's why you see your action reflected in other items.

The solution:

Set to your tag, not the viewHolder, but the item position. Then, in your Data structure, you should add variables to remember which buttons should be displayed or not.

class Data {
    ...
    String text;
    boolean btnVisible;
}

In View.OnClickListener you should set these variables values.

private View.OnClickListener onItemClickLIstener = new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        int position =  (int)view.getTag();
        Data data = dataList.get(position);
        data.btnVisible = false;
        adapter.notifyDataSetChanged();
        // TODO: eventually do some logic here with the data
    }
}

Then, in your onBindViewHolder you should set the button text and the button visibility state depending on variables in Data structure.

@Override
public void onBindViewHolder(@NonNull RecycleAdapter.ViewHolder viewHolder, int position) {
    Data data = dataList.get(position);
    // Do stuff with data

    myButton.setText(data.text);
    myButton.setVisibility(?data.btnVisible:VISIBLE:GONE);
}

Upvotes: 2

Parth Pitroda
Parth Pitroda

Reputation: 997

please use below snip for get exact position, setTag(); to the View and then on Click time get that tag value using getTag(); for perfect position.

    myViewHolder.ivVoiceThumb.setTag(pos);
    myViewHolder.ivVoiceThumb.setOnClickListener(view -> {
        int getPos = (int)view.getTag();
    });

Upvotes: 2

Related Questions