The constructor of my RecyclerView's adapter looks like:
Context context;
List<ConnectionItem> connections;
public ConnectionsListAdapter(Context context, List connections) {
this.context = context;
this.connections = connections;
}
After the adapter's declarations, I declare a static ViewHolder class for the RecyclerView, which also handles the onClicks of any buttons:
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public Context context;
public ImageView connectionImage;
public TextView connectionName;
public ImageButton startMsg;
public ViewHolder(View itemView, List<ConnectionItem> connections) {
super(itemView);
...
startMsg.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(v.getContext(), ChatActivity.class);
intent.putExtra("name", connections.get(getAdapterPosition()).getName()); // Error because accessing from static context
intent.putExtra("id", connections.get(getAdapterPosition()).getUid()); // Error because accessing from static context
context.startActivity(intent);
}
}
The problem is that connections
is not accessible from a static context in the ViewHolder static class. What's the best way for my ViewHolder to get information from the RecyclerView adapter? As a workaround, I am passing the data source to the constructor of the ViewHolder and having a new instance variable in the ViewHolder for the data source:
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public Context context;
public List<ConnectionItem> connections;
public ImageView connectionImage;
public TextView connectionName;
public ImageButton startMsg;
public ViewHolder(View itemView, List<ConnectionItem> connections) {
super(itemView);
this.connections = connections;
...
startMsg.setOnClickListener(this);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(v.getContext(), ChatActivity.class);
intent.putExtra("name", connections.get(getAdapterPosition()).getName()); // Okay now
intent.putExtra("id", connections.get(getAdapterPosition()).getUid()); // Okay now
context.startActivity(intent);
}
Does my way at all break the ViewHolder pattern or create any other problems in the future?
A ViewHolder describes an item view and metadata about its place within the RecyclerView. Adapter implementations should subclass ViewHolder and add fields for caching potentially expensive findViewById results. While LayoutParams belong to the LayoutManager , ViewHolders belong to the adapter.
Go to the res -> layout -> activity_main.
Your responsibilities/concerns are a little scrambled.
In MVC terms, a ViewHolder
is really a View. It's just an object that has references to the subviews so that findViewById()
doesn't have to be called over and over. But it's still really a view.
However, you have a constructor with Model data as an argument, and that has a bad smell to it.
The only properties/variables your ViewHolder
should have are View
s.
The only thing your ViewHolder
constructor should ever be calling is findViewById()
You haven't really talked about your adapter much, but you'll notice that you override two methods: onCreateViewHolder
and onBindViewHolder
.
Within onCreateViewHolder
you construct the ViewHolder
that's applicable to your view type. That's it. You may have to inflate different layouts depending on the view type, but you are just dealing with views here. Don't pass any Model data to the ViewHolder
in the constructor.
Within onBindViewHolder
, here is where you hook up the View to the Model data.
Something that I do frequently is define a bind()
method for my ViewHolder
so that it's very clear what Model data is being handed off. The bind()
method takes the data and calls setText
and similar methods to make the views reflect the Model data for that adapter position.
But now we come to the ugly part of RecyclerView
's Adapter
design. There is no OnItemClickListener
for the adapter.
Google argues that this is a better design; that events should be handled by the list item view anyway. I get that. But the problem is that the event has to meet up with its model data, and it's the adapter that has the model data, not the list item view.
And Google has emphasized that you can't use final
on values like the position; you need to call getAdapterPosition()
in order to index the model data.
So now I have gone to a pattern that accommodates all these constraints.
I define an interface
for an event listener with a method that takes the position argument.
I create an instance of the listener in the adapter
Every time I bind to a ViewHolder
I pass this listener instance (so the ViewHolder
does have a reference to the listener, but not the adapter)
On the event handler in the ViewHolder
, I call getAdapterPosition()
to get the position of the list item, then I call the listener method with that position
The adapter gets the listener callback, accesses the correct model data and performs the desired action
So here's an example: I have a list of items that have been selected, i.e. products in an online shopping cart that have a Remove button with a big X. Now I have to handle that remove button.
Define the interface:
interface OnItemRemovedListener {
void itemRemoved(int position);
}
Create an instance of the listener in the adapter
private OnItemRemovedListener mCallback;
Set it up in the adapter constructor:
mCallback = new OnItemRemovedListener() {
@Override
public void itemRemoved(int position) {
mItemList.remove(position);
notifyDataSetChanged();
}
};
The ViewHolder
subclass:
public static class ProductItemViewHolder extends RecyclerView.ViewHolder {
private TextView mProductName;
private Button mRemoveButton;
private OnItemRemovedListener mListener;
public ProductItemViewHolder(View itemView) {
super(itemView);
mProductName = (TextView) itemView.findViewById(R.id.product_name);
mRemoveButton = (Button) itemView.findViewById(R.id.remove_button);
}
public void bind(Model data, OnItemRemovedListener listener) {
mProductName.setText(data.getProductName());
mListener = listener;
mRemoveButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
int position = getAdapterPosition();
if (position >= 0) {
mListener.itemRemoved(position);
}
}
});
}
}
Then onBindViewHolder
override looks like this:
@Override
public void onBindViewHolder(ProductItemViewHolder holder, int position) {
holder.bind(mItemList.get(position), mCallback);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With