So what is the difference between RecyclerView.Adapter< MyAdapter.MyViewHolder> and RecyclerView.Adapter and what is the syntax<> in RecyclerView.Adapter means in this case? I know it stands for generics. When should I use this?
First, let's review the definition of the Adapter
class. It's defined within RecyclerView
as so:
public abstract static class Adapter<VH extends ViewHolder>
So what does the "generics" part of this mean? It means the Adapter class operates on some class type (VH
) that must be a descendant of the class ViewHolder
. This is known as a Bounded Type and ensures that the type of of class the adapter is working with is guaranteed to be a type of ViewHolder
.
As you see with the example on the developer's site you posted, when you create your own adapter that specifies the type of the ViewHolder as MyViewHolder
, then the methods you have to override in the adapter explicitly declare that type in their signatures. For example, the return type for onCreateViewHolder
is explicitly set to MyViewHolder
.
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
TextView v = (TextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
Alternatively, the tutorial you posted does not specify a type.
public class SimpleAdapter extends RecyclerView.Adapter
When you extend a class that expects a generic type and don't specify one, the compiler defaults to the least common denominator - that is, the class type lowest in the inheritance hierarchy that will guarantee the class can be used as designed. Because the Adapter's generic type is defined as VH extends ViewHolder
, the compiler knows that the class type must be at least a ViewHolder
and defaults to that. Hence, the same method overriden for this example returns RecyclerView.ViewHolder
instead of MyViewHolder
:
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new SimpleViewHolder(view);
}
If RecyclerView.Adapter
was defined as Adapter<VH>
(no bounded type), and you extended it without specifying the type, the type would default to Object
(the root class of all Java classes):
@Override
public Object onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return new SimpleViewHolder(view);
}
So, finally, why use one instead of the other? Generally speaking: you should always specify the type for a generic class. This guarantees you type-safety. In the tutorial example, you could return the wrong type of view holder which would compile but crash at runtime:
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
// Returning wrong view holder, but because it still extends ViewHolder and
// that is all this method requires, this compiles
return new SomeOtherViewHolder(view);
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
// Above we created a SomeOtherViewHolder but here we're expecting a SimpleViewHolder
// This will crash trying to cast to the wrong type
((SimpleViewHolder) holder).bindData(models.get(position));
}
It's impossible to make this mistake in the first example though because the ViewHolder
type is declared explicitly:
@Override
public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
TextView v = (TextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.my_text_view, parent, false);
// Trying to return SomeOtherViewHolder when method expects MyViewHolder
// Compiler's like (*waves finger*) "nuh-uh, not on my watch" and fails
// to compile
SomeOtherViewHolder vh = new SomeOtherViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
// Notice here that holder is explicitly of MyViewHolder type and casting
// is not necessary! That's TYPE SAFETY y'all.
holder.textView.setText(mDataset[position]);
}
Because you specify the explicit, concrete type you expect, you cannot accidentally return the wrong type, you do not need to cast, and you can leverage all of the public methods defined in your custom ViewHolder
class.
Hope that helps!