Not bad RecyclerView Practices 1: OnItemClickListener


## 0. tl,dr:
I created a repo: [RVP](https://github.com/eneim/RVP)
Where the most important classes are stored in ```core```:
```BaseAdapter.java``` - will generally support basic RecyclerView#Adapter's behavior. It comes with a RecyclerView's version of OnItemClickListener (I was inspired by AdapterView's interface).
```BaseListAdapter.java``` - an extend of ```BaseAdapter```, focuses on List of a specific Data type.
Some examples are included outside, take a look at package ```fragment``` and ```adapter```.

## 1. So we Click Item, but we don't know the good way to listen to that Event

+ Problem and My Opinions: 
	- Since the release of ```RecyclerView``` and its childs (Other libraries based on ```RecyclerView```), there are various ways to interaction to item, and there are event more ways to listen to those event. -- **My Opinion**: The need of a ```On Item Click Listener``` thing is obvious, but there's not been a globally good practice (which is good for all cases) already.
	- Many Developers keep in mind that ```RecyclerView``` is a Updated version of ```ListView``` and ```GridView```, so they expect some similar behavior of those Views on ```RecyclerView```. -- **My Opinion**: this thought is not always true. RecyclerView is a superset of a general ```AdapterView``` - the View which requires Adapter to populate its UI. RecyclerView can do a lot more than the legacy ```AdapterView```. Further more, current implementations of ```OnItemClickListener``` are trying to mimic the famous ```AdapterView#setOnItemClickListener()``` setup, which is generally not that bad, but we should not rely on or try to reproduce it by all means.

+ My Solution: in short, instead of trying to mimic this setup ```AdapterView#setOnItemClickListener()```, I use ```RecyclerView$Adapter``` instead, which will become something like this: ```MyAdapter#setOnItemClickListener()```.

## 2. Base classes

*talk is cheap, so me some codes*
 
```Java:BaseAdapter.class
/**
 * A less abstract Adapter, to simplify RecyclerView#Adapter implementation
 */
public abstract class BaseAdapter<VH extends BaseAdapter.ViewHolder>
    extends RecyclerView.Adapter<VH> {

  /**
   * This custom onClick event listener should be set by the Adapter
   */
  protected OnItemClickListener mOnItemClickListener;

  public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
    this.mOnItemClickListener = onItemClickListener;
  }

  /**
   * An abstract ViewHolder
   */
  public abstract static class ViewHolder extends RecyclerView.ViewHolder {

    public ViewHolder(@NonNull View itemView) {
      super(itemView);
    }

    /**
     * This method is supposed to be override every time. For example, a view holder holds more
     * than 2 views, and Client want to listen to the click event on each of them.
     * <p/>
     * By default, the main #itemView will receive the click event.
     * <p/>
     * !IMPORTANT: This method is used optionally.
     *
     * @param listener to listen to Click event
     */
    // NOTE: Long name, I know
    public void setOnViewHolderClickListener(View.OnClickListener listener) {
      itemView.setOnClickListener(listener);
    }
  }

  public interface OnItemClickListener {

    /**
     * Interact with RecyclerView's item on click event
     *
     * @param adapter         who holds data
     * @param viewHolder      the #ViewHolder who receives click event
     * @param view            the view under the click event
     * @param adapterPosition position of clicked item in adapter
     * @param itemId          retrieve from
     *                        {@link android.support.v7.widget.RecyclerView.Adapter#getItemId(int)}
     */
    void onItemClick(BaseAdapter adapter, ViewHolder viewHolder, View view,
                     int adapterPosition, long itemId);
  }
}
```

Just a simple extends of ```RecyclerView$Adapter```, where I define a *kind of* OnItemClickListener interface with the main character: ```onItemClick``` method. This method takes the ```Adapter``` as a neccessary member, where we could retrieve data and more. !NOTE: Replacing ```Adapter``` by RecyclerView it self is Ok, since we could get ```Adapter``` from it. But keep in mind that a ```RecyclerView``` holds a lot more memory than an ```Adapter```.

Here we also have a simple implementation of ViewHolder, which supports seting the interaction event setup. The default setup will be the Click event on ```ViewHolder#itemView```, but Client could (and should) override this to provide more expected behaviors (Take a look at my Sample classes).

This Based setup will struct a vision for Client to implement it own Click listener on Children classes.

```Java:BaseListAdapter.class
/**
 * @param <T> This Adapter is specified to use with List of Objects of type T
 */
public abstract class BaseListAdapter<T> extends BaseAdapter<BaseListAdapter.ViewHolder<T>> {

  /**
   * This Adapter must support retrieving item(s)
   * <p/>
   * !IMPORTANT General Adapter could support various Types of Object, so we must not force it to
   * return a single Type of object. This BaseListAdapter was created to support those cases.
   *
   * @param position of the item we want to get
   * @return expected Item at a position
   */
  public abstract T getItem(int position);

  /**
   * If Client implement this method, He must call super.onBindViewHolder for expected
   * behaviors.
   *
   * @param holder
   * @param position
   */
  @CallSuper
  @Override public void onBindViewHolder(ViewHolder<T> holder, int position) {
    T item = getItem(position);
    if (item != null) {
      holder.bindInternal(item);
    }
  }

  /**
   * For now we don't support this method.
   *
   * @param holder
   * @param position
   * @param payloads
   */
  /*hide*/
  @Override
  public void onBindViewHolder(ViewHolder<T> holder, int position, List<Object> payloads) {
    super.onBindViewHolder(holder, position, payloads);
  }

  /**
   * General abstract ViewHolder to support specific Data type
   *
   * @param <T> expected Data Type
   */
  public abstract static class ViewHolder<T> extends BaseAdapter.ViewHolder {

    // I think it's not bad to have an shallow copy of current Data
    protected T mItem;

    public ViewHolder(@NonNull View itemView) {
      super(itemView);
    }

    // This method will always be called by Adapter
    void bindInternal(T item) {
      mItem = item;
      bind(item);
    }

    // Client then update its ViewHolder's appearance here
    public abstract void bind(T item);
  }
}
```

This class is an simple extension of the ```BaseAdapter``` above, in respect to ```android.widget.ArrayAdapter```. ```BaseListAdapter``` suppose to support a specific Data Type ***T***, come along with a special ```ViewHolder``` class to ensure that each ```ViewHolder``` will represent an Object of type ***T***. ```ViewHolder#bindInternal``` are called to make sure that a ```ViewHolder``` instance hold its ```mItem``` for later use. *This implementation is Optional.*

2 Base Classes above are how I prepare the base for any further interaction implementation. In fact, the same implementation could be done to support ```OnItemLongClickListener```.

## 3. OnItemClick in action

I prepared 2 Fragment, who hold 2 lists: a Simple list of String and a more complicated list of *Cheese* (borrow the idea, some pieces of code and resources from [Cheesesquare](https://github.com/chrisbanes/cheesesquare)).

Let's start from something complicated :D.

- CheeseListFragment and CheeseListAdapter: 

An implementation of ViewHolder for a Cheese list:

```Java:CheeseListAdapter$ViewHolder.class
public static class ViewHolder extends BaseListAdapter.ViewHolder<Cheeses> {

    // A ViewHolder expects a UI Layout, so make it static here
    static final int LAYOUT_RES = R.layout.list_item;

    public final ImageView mImageView;
    public final TextView mTextView;

    public ViewHolder(View view) {
      super(view);
      mImageView = (ImageView) view.findViewById(R.id.item_icon);
      mTextView = (TextView) view.findViewById(R.id.item_name);
    }

    @Override public void bind(Cheeses item) {
      mTextView.setText(item.getName());
      Picasso.with(itemView.getContext())
          .load(item.getIconRes())
          .fit().centerCrop()
          .into(mImageView);
    }

    // !IMPORTANT Since we accept click event on 2 different views, we must delegate them here.
    @Override public void setOnViewHolderClickListener(View.OnClickListener listener) {
      mImageView.setOnClickListener(listener);
      itemView.setOnClickListener(listener);
    }
  }
```

This ViewHolder is exactly the same as *Cheesesquare*'s list view holder. But it provides better interaction: click on Cheese's image and click on the item's cell. To support that, we simply override ```#setOnViewHolderClickListener``` and setup the normal OnClick event for whatever Views from which we want to listen to click event. Here I setup for my **mImageView** and the **itemView** (which holds **mImageView** as its child).

```Java:CheeseListAdapter.class
  // Just because I like this number
  private static final int LIST_SIZE = 23;

  private static final List<Cheeses> sItems = getRandomSublist(Cheeses.sCheeseStrings, LIST_SIZE);

  @Override public Cheeses getItem(int position) {
    return sItems.get(position);
  }

  @Override
  public BaseListAdapter.ViewHolder<Cheeses> onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext())
        .inflate(ViewHolder.LAYOUT_RES, parent, false);
    final ViewHolder viewHolder = new ViewHolder(view);
    // setup Click event listener here
    viewHolder.setOnViewHolderClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        if (CheeseListAdapter.this.mOnItemClickListener != null) {
          int adapterPosition = viewHolder.getAdapterPosition();
          // get this from Android Summit Video
          if (adapterPosition != RecyclerView.NO_POSITION) {
            CheeseListAdapter.this.mOnItemClickListener.onItemClick(
                CheeseListAdapter.this, viewHolder, v, adapterPosition, getItemId(adapterPosition)
            );
          }
        }
      }
    });
    return viewHolder;
  }

  /**
   * Customized Click event listener
   */
  public abstract static class OnCheeseClickListener implements OnItemClickListener {

    public abstract void onIconClick(View iconView, Cheeses cheese);

    public abstract void onCheeseClick(View nameView, Cheeses cheese);

    @Override
    public void onItemClick(BaseAdapter adapter, BaseAdapter.ViewHolder viewHolder,
                            View view, int adapterPosition, long itemId) {
      if (adapter instanceof BaseListAdapter) {
        // we expect a ListAdapter here, since we are using a List Adapter
        BaseListAdapter listAdapter = (BaseListAdapter) adapter;
        // so we can get clicked item
        Cheeses item = (Cheeses) listAdapter.getItem(adapterPosition);

        // Note 1: Casted object maybe null if we're using wrong adapter
        // Note 2:
        if (item != null && viewHolder instanceof ViewHolder) {
          if (view == ((ViewHolder) viewHolder).mImageView) {
            onIconClick(view, item);
          } else if (view == ((ViewHolder) viewHolder).itemView) {
            onCheeseClick(view, item);
          }
        }
      }
    }
  }

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

  List<Cheeses> getRandomSublist(String[] array, int amount) {
    ArrayList<Cheeses> list = new ArrayList<>(amount);
    ... // refer Cheesesquare
    return list;
  }

  public static class ViewHolder extends BaseListAdapter.ViewHolder<Cheeses> {
	 ... // above
  }
}
```

So for my custom click listener behavior, I have ```OnCheeseClickListener``` which implement ```BaseAdapter$OnItemClickListener```. By that, I could provide custom behavior for each components in each of this list's ViewHolder objects.

```CheeseListAdapter#onCreateViewHolder``` is where we pass the Click event listener from parent Adapter to its ViewHolder. Inspired from ```Android Summit 2015``` talks, that is where we should do that works, and ```ViewHolder#getAdapterPosition()``` is the position we should use to get Data.

Now, take a look at ```CheeseListFragment```

```Java:CheeseListFragment.class
public class CheeseListFragment extends Fragment {

  RecyclerView mRecyclerView;
  RecyclerView.LayoutManager mLayoutManager;
  CheeseListAdapter mAdapter;
  /**
   * My customized click listener. We can directly handle click event from Fragment or Setup
   * another callback to attach to Host Activity's lifecycle. IMO, Both are good practices.
   */
  private CheeseListAdapter.OnCheeseClickListener mClickListener;

  public CheeseListFragment() {
  }

  public static CheeseListFragment newInstance() {
    return new CheeseListFragment();
  }

  @Nullable @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
      savedInstanceState) {
    return inflater.inflate(R.layout.fragment_cheese_list, container, false);
  }

  @Override public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
    /*
      We can try both UI to check if our position works as expected;
     */
    // mLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
    mLayoutManager = new GridLayoutManager(getContext(), 2, LinearLayoutManager.VERTICAL, false);
    // a little bit more complicated Grid.
    GridLayoutManager.SpanSizeLookup spanSizeLookup = new GridLayoutManager.SpanSizeLookup() {
      @Override public int getSpanSize(int position) {
        return position % 3 == 2 ? 2 : 1;
      }
    };

    ((GridLayoutManager) mLayoutManager).setSpanSizeLookup(spanSizeLookup);

    mRecyclerView.setLayoutManager(mLayoutManager);

    mAdapter = new CheeseListAdapter();
    mRecyclerView.setAdapter(mAdapter);

    mClickListener = new CheeseListAdapter.OnCheeseClickListener() {
      @Override public void onIconClick(View iconView, Cheeses cheese) {
        if (getView() != null) {
          Snackbar.make(getView(), "Icon clicked: " + cheese.getName(), Snackbar.LENGTH_LONG)
              .show();
        }
      }

      @Override public void onCheeseClick(View nameView, Cheeses cheese) {
        Intent intent = new Intent(getContext(), CheeseDetailActivity.class);
        intent.putExtra(CheeseDetailActivity.EXTRA_NAME, cheese.getName());
        startActivity(intent);
      }
    };

    mAdapter.setOnItemClickListener(mClickListener);
  }

  @Override public void onDestroyView() {
    // This click event is attached to UI behavior, so we should properly release it before all
    // views are dead.
    // IMO, Doing this in onDestroyView or onDetach is really depends on how you use your listener.
    mClickListener = null;
    super.onDestroyView();
  }
}
```

There's been various practices on how to pass logic to Fragment from Activity. I don't discuss it here. The point here is how we pass that event to our Adapter, which is simply like this:

```Java
CheeseListAdapter.OnCheeseClickListener  mClickListener = new CheeseListAdapter.OnCheeseClickListener() {
      @Override public void onIconClick(View iconView, Cheeses cheese) {
        if (getView() != null) {
          Snackbar.make(getView(), "Icon clicked: " + cheese.getName(), Snackbar.LENGTH_LONG)
              .show();
        }
      }

      @Override public void onCheeseClick(View nameView, Cheeses cheese) {
        Intent intent = new Intent(getContext(), CheeseDetailActivity.class);
        intent.putExtra(CheeseDetailActivity.EXTRA_NAME, cheese.getName());
        startActivity(intent);
      }
    };

    mAdapter.setOnItemClickListener(mClickListener);
```

For the general ```Activity-Fragment-Callback``` implementation, we can have an interface from our Fragment, and retrieve its instance by ```onAttach```, I leave it for Client. But it would not be complicated.

Above example looks complicated, now we look at a much more simpler Example: A simple String list

```Java: SimpleListAdapter$ViewHolder.class
public static class ViewHolder extends BaseListAdapter.ViewHolder<String> {

    // This is a TextView's layout
    static final int LAYOUT_RES = android.R.layout.simple_list_item_1;

    public ViewHolder(@NonNull View itemView) {
      super(itemView);
    }

    @Override public void bind(String item) {
      // It's safe here
      ((TextView) itemView).setText(item);
    }
  }
```

Comparing to Cheese list's Viewholder, we have simpler class here, without any setup for On Click event. Because by default, ```ViewHolder#itemView``` will handle that event if set (```BaseAdapter$ViewHolder#setOnViewHolderClickListener()```).

```Java:SimpleListAdapter.class
public class SimpleListAdapter extends BaseListAdapter<String> {

  List<String> getRandomSublist(String[] array, int amount) {
    ArrayList<String> list = new ArrayList<>(amount);
    ... // refer Cheesesquare
    return list;
  }

  // Just because I like this number
  private static final int LIST_SIZE = 23;

  private final List<String> sItems = getRandomSublist(Cheeses.sCheeseStrings, LIST_SIZE);

  @Override public String getItem(int position) {
    return sItems.get(position);
  }

  @Override
  public BaseListAdapter.ViewHolder<String> onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext())
        .inflate(ViewHolder.LAYOUT_RES, parent, false);
    final ViewHolder viewHolder = new ViewHolder(view);
    viewHolder.setOnViewHolderClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        int adapterPosition = viewHolder.getAdapterPosition();
        // get this from Android Summit Video
        if (adapterPosition != RecyclerView.NO_POSITION) {
          SimpleListAdapter.this.mOnItemClickListener.onItemClick(
              SimpleListAdapter.this, viewHolder, v, adapterPosition, getItemId(adapterPosition)
          );
        }
      }
    });

    return viewHolder;
  }

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

  public static class ViewHolder extends BaseListAdapter.ViewHolder<String> {
	... // above
  }
}
```

So except for the custom ```OnCheeseClickListener```, this class is the same as ```CheeseListAdapter```, with a little mount of code. The most important part was still the ```#onCreateViewHolder``` implementation.

```Java:SimpleListFragment.class
public class SimpleListFragment extends Fragment {

  public SimpleListFragment() {
  }

  public static SimpleListFragment newInstance() {
    return new SimpleListFragment();
  }

  RecyclerView mRecyclerView;
  RecyclerView.LayoutManager mLayoutManager;
  SimpleListAdapter mAdapter;
  BaseAdapter.OnItemClickListener mClickListener;

  @Nullable @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
      savedInstanceState) {
    return inflater.inflate(R.layout.fragment_cheese_list, container, false);
  }

  @Override public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
    mLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
    mRecyclerView.setLayoutManager(mLayoutManager);

    mAdapter = new SimpleListAdapter();
    mRecyclerView.setAdapter(mAdapter);

    mClickListener = new BaseAdapter.OnItemClickListener() {
      @Override
      public void onItemClick(BaseAdapter adapter, BaseAdapter.ViewHolder viewHolder, View view,
                              int adapterPosition, long itemId) {
        String item = null;
        if (adapter instanceof SimpleListAdapter) {
          item = ((SimpleListAdapter) adapter).getItem(adapterPosition);
        }

        if (item != null) {
          Intent intent = new Intent(getContext(), CheeseDetailActivity.class);
          intent.putExtra(CheeseDetailActivity.EXTRA_NAME, item);
          startActivity(intent);
        }
      }
    };

    mAdapter.setOnItemClickListener(mClickListener);
  }

  @Override public void onDestroyView() {
    // This click event is attached to UI behavior, so we should properly release it before all
    // views are dead.
    // IMO, Doing this in onDestroyView or onDetach is really depends on how you use your listener.
    mClickListener = null;
    super.onDestroyView();
  }
}
```

Here we have almost the same structure with ```CheeseListFragment```. And again this is not a bunch of code ***in my opinion*** :trollface:.

## 4. Conclusion and Future works

This is neither a new implementation nor a new Idea. It's just another way to handle the famous click event, created specially for ```RecyclerView```. This is based on **one point** from ```Android Summit 2015``` talk, where ```RecyclerView```'s creator pointed out where should be the place we setup the click event listener. The rest of this project tries to support our common sense of setting up our legacy ```AdapterView```. But it doesn't try to attach on ```AdapterView``` point of view, but it goes by the think of ```RecyclerView```: ```Adapter``` is where we start everything, ```RecyclerView``` will support the hard works. So by this point of view, I hope this implementation could be considered a *not bad practices*.

More works need to be done for more interaction event (long click, for example). And there are more rooms for others **NOT BAD RECYCLERVIEW PRACTICES** too (At least, I was talking about ***OnClickListener*** this time only). So hopefully more practices are coming.

## 5. Last but not least

Re: I created a repo: [RVP](https://github.com/eneim/RVP)

This project is written in plain MVC, so it might not look good or even useful in other design patterns though. So use it at your own risk, and any contribution are welcome.

This project follows Google's Java coding style (*tl,dr: 2-spaces-indent*) :D.