0

I'm learning RecyclerView and in developer's site the Adapter class extends RecyclerView.Adapter<MyAdapter.MyViewHolder>. The implementation shows:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private String[] mDataset;


public static class MyViewHolder extends RecyclerView.ViewHolder {

    public TextView textView;
    public MyViewHolder(TextView v) {
        super(v);
        textView = v;
    }
}

public MyAdapter(String[] myDataset) {
    mDataset = myDataset;
}

@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;
}

@Override
public void onBindViewHolder(MyViewHolder holder, int position) {

    holder.textView.setText(mDataset[position]);

}

@Override
public int getItemCount() {
    return mDataset.length;
}
}

And in this tutorial, the adapter class extends RecyclerView.Adapter. The implementation is:

public class SimpleAdapter extends RecyclerView.Adapter {

private List<SimpleViewModel> models = new ArrayList<>();


public SimpleAdapter(final List<SimpleViewModel> viewModels) {
    if (viewModels != null) {
        this.models.addAll(viewModels);
    }
}


@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);
}


@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
    ((SimpleViewHolder) holder).bindData(models.get(position));
}


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


@Override
public int getItemViewType(final int position) {
    return R.layout.item_simple_itemview;
}
}

So what is the difference between RecyclerView.Adapter<MyAdapter.MyViewHolder> and RecyclerView.Adapter and what is the syntax<> in RecyclerView.Adapter<MyAdapter.MyViewHolder> means in this case? I know it stands for generics. When should I use this?

Xenon Kfr
  • 1,925
  • 1
  • 15
  • 13
  • 1
    If a class is a generic type, you should always specify the type arguments. See [What is a raw type and why shouldn't we use it?](https://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it). – Slaw Jan 26 '20 at 14:47
  • So I should use "public class SimpleAdapter extends RecyclerView.Adapter" instead of "public class SimpleAdapter extends RecyclerView.Adapter "? It works both ways. – Xenon Kfr Jan 26 '20 at 15:00
  • 1
    You should use the first, though you'd obviously replace the type argument with whatever type your application is using. While it works both ways they are not equivalent—the second is a _raw type_. See the Q&A I linked in my previous comment for more information about raw types and why they should be avoided. – Slaw Jan 27 '20 at 05:18

1 Answers1

1

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!

dominicoder
  • 9,338
  • 1
  • 26
  • 32