DEV Community

Cover image for Adding OnClickListener to RecyclerView in Android
Tristan Elliott
Tristan Elliott

Posted on • Updated on

Adding OnClickListener to RecyclerView in Android

Introduction

  • This series is going to be dedicated to the basic of Android development. Join me and let us try to build and understand some cool stuff. All the resources I used to create this post can be found on ticketnote or HERE.

  • I also want to mention one final thing that I except from you the reader. Before reading this you should have a solid understanding of Android Fragments and the RecyclerView. I mention this because I will not be going over fragment transactions or how to implement a RecyclerView. Check out HERE for my fragments tutorial and HERE for my RecyclerView tutorial. If you do not understand these fundamentals then you may find it hard to follow along in this tutorial.

YouTube Version

An abstract view of what we are doing

  • Basically what we are doing is defining a method passing the method to our CustomAdapter instance, then to the createViewHolder() method and finally to the ViewHolder instance. This method will get called when each individual ViewHolder object is clicked. We of course will provide a much more technical implementation but generally speaking that is what we are doing.

The Fragment methods that we are using.

  • Now before we get into actually implementing the method I want to talk about the fragment methods we are going to use in our implementation. Examples of our fragment's code are below:
public class ExampleFragment extends Fragment implements CustomAdapter.OnNoteListener{
    private List<String> data;

    public ExampleFragment(){
        super(R.layout.example_fragment);
    }

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        //This is where we initialize the data. Normally this would be from a remote server
        dataCall();

    }

    @Override
    public void onViewCreated(View view,Bundle savedInstanceState){
        // this is where we are going to set the RecyclerView
        RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.example_view);
       recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
       recyclerView.setAdapter(new CustomAdapter(this.data,this));

    }

    public void dataCall(){
        ArrayList<String> data = new ArrayList<>();
        for(int i = 0; i< 49; i ++){
            data.add("NAME # " +i);
        }
        this.data = data;

    }

    @Override
    public void onNoteClick(int position) {
        Toast toast = Toast.makeText(getActivity(),"CLICKED",Toast.LENGTH_SHORT);
        toast.show();

    }
}
Enter fullscreen mode Exit fullscreen mode
  • This may seem like a lot of code but for now only focus on the onCreate(), onViewCreated() methods and the class constructor.
  • Also if you are unfamiliar with the fragment lifecycle then I highly suggest that you read about it HERE. From this point on I will assume you have a solid understanding of fragments, the fragment lifecycle and recyclerviews

onCreate(Bundle,savedInstanceState)

  • When a fragment is added to the Fragment Manager (fragment manager is responsible for organizing and calling methods in fragments) it enters the CREATED state of its lifecycle. This transition into the state triggers the onCreate() method. The method receives a Bundle which contains any previously saved state but initially the value is null. This method is also called before onCreateView()(which we do not have, more on that later) and called before onViewCreated()
  • Now I am not a 100% sure why but in the example documentation HERE we are told to initialize data sets in this method. That means that this is the method we would normally make a server call to, or in our case set up an ArrayList.

onCreateView(LayoutInflater, ViewGroup, Bundle)

  • This method is called when the view is going to be inflated(created). The documentation recommends that we only use this method for inflating the view and then move the logic to onViewCreated().
  • The question you are probably asking your self is, "if this method is used for inflating the view then why don't we have one?". We do have this method, it is just being called automatically for us. Proof of this is in the constructor:
public ExampleFragment(){
        super(R.layout.example_fragment);
    }

Enter fullscreen mode Exit fullscreen mode
  • When we make a super call of super(R.layout.example_fragment) we are invoking an alternative constructor within the Fragment class which will take the View that we provided it and automatically call onCreateView() for us.

onViewCreated(View, Bundle)

  @Override
    public void onViewCreated(View view,Bundle savedInstanceState){
        // this is where we are going to set the RecyclerView
        RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.example_view);
       recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
       recyclerView.setAdapter(new CustomAdapter(this.data,this));

    }
Enter fullscreen mode Exit fullscreen mode
  • This method gets called immediately after onCreateView() has returned and receives the inflated view from it. We can use that inflated view to set up our RecyclerView.

  • Now we can move on to creating the method that will get passed to our adapter and eventually the ViewHolder object.

Creating our method.

  • First I am going to paste the code and then talk about the part pertaining to creating our method.
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
    private List<String> data;
    private OnNoteListener onNoteListener;

    public CustomAdapter(List<String> data,OnNoteListener onNoteListener){
        this.data = data;
        this.onNoteListener = onNoteListener;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
        private final TextView textView;
        OnNoteListener onNoteListener;

        public ViewHolder(View view,OnNoteListener onNoteListener){
            super(view);
            this.textView = (TextView) view.findViewById(R.id.text_view_id);
            view.setOnClickListener(this);
            this.onNoteListener = onNoteListener;
        }

        @Override
        public void onClick(View v) {
            this.onNoteListener.onNoteClick(getAdapterPosition());
        }
    }


    @Override
    public CustomAdapter.ViewHolder onCreateViewHolder( ViewGroup parent, int viewType) {
        //inflate the indiv item
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.indiv_viewholder_item,parent,false);

        return new ViewHolder(view, this.onNoteListener);
    }

    @Override
    public void onBindViewHolder( CustomAdapter.ViewHolder holder, int position) {
        holder.textView.setText(this.data.get(position));

    }

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

    public interface OnNoteListener{
        void onNoteClick(int position);
    }
}

Enter fullscreen mode Exit fullscreen mode
  • This is our entire custom adapter but the part we only care about is the interface OnNoteListener:
public interface OnNoteListener{
        void onNoteClick(int position);
    }
Enter fullscreen mode Exit fullscreen mode
  • After seeing this interface, you probably have 2 questions, 1) why is it nested inside the CustomAdapter class and 2) why are we using an interface?

1) Why is it nested inside the CustomAdapter class ?

  • Generally in Java we nest something inside of a class because it and the class have a close relation. This is the case for us because the interface really has no use outside of the context of our CustomAdapter class. This then leads us to our next question

2) why are we using an interface ?

  • Before we can answer this question we first have to define what an interface is. A interface allows a class to be more formal about the behaviour a class provides. Interfaces form a contract between a class and the outside world. This contract is enforced at build time by the compiler. Basically when we use an interface on a class we are making a formal declaration on what methods it will be able to use and every class that implements the interface will also have those methods. So now that we know what an interface is, we have to understand why we would want to use an interface. Generally, it would be one of three reasons.

1) Unrelated classes need to have similar implementations.

2) You want to specify the behaviour of a particular data type but you are not concerned about who implements the behaviour.

3) You want to take advantage of multiple inheritance of a type.

  • For our case it is 2, we want to specify a behaviour but we are not concerned about who implements the behaviour. Also, I want to point out that interfaces can also be used as reference data types. Same as a class.

Implementing the Interface

  • Now if we look back at our Fragment class you should notice that we implement an interface:
public class ExampleFragment extends Fragment implements CustomAdapter.OnNoteListener
Enter fullscreen mode Exit fullscreen mode
  • When we implements an interface, we have to use CustomAdapter.onNoteListener because our interface is nested inside of our CustomAdapter class. Once we have implements our interface we must implement all of the interfaces methods, for us it is onNoteClick.
public void onNoteClick(int position) {
        Toast toast = Toast.makeText(getActivity(),"CLICKED",Toast.LENGTH_SHORT);
        toast.show();

    }
Enter fullscreen mode Exit fullscreen mode
  • This method is the method that we have been talking about. It is the method that we are going to pass around until it gets passed to the ViewHolder object. This method that is getting called when we click on the individual ViewHolder objects. We pass it an int to show that each method can call it with its own values. However, we are only using it to create and show a toast when clicked. Toast.makeText() is used to create a Toast, it takes 3 parameters: the context, the text to be shown and the duration that the toast will be viewed. toast.show() must be called in order for the toast to be shown.

  • Now remember that our goal is to simply pass a method to our ViewHoder object so that it gets called on every click. In order to do that we must to pass access to our method to the CustomAdapter object and then to the onCreateViewHolder() method and then finally to the ViewHolder object its self.

Passing access to our CustomAdapter

recyclerView.setAdapter(new CustomAdapter(this.data,this));
Enter fullscreen mode Exit fullscreen mode
  • This is just us setting our Adapter, this.data is us using the instance variable called data as an argument for the the CustomAdapter. Now what is this? Well, any time that 'this' is used inside of an instance method or constructor it references the current object. So when use this we are referencing public class ExampleFragment extends Fragment implements CustomAdapter.OnNoteListener.
  • A quick reminder that we are also able to use a classes interface as its type when referencing it. I mention this because inside of our CustomAdapter constructor we are using the type of OnNoteListener and setting a global instance variable inside or our CustomAdapter class, see below:
private OnNoteListener onNoteListener;

    public CustomAdapter(List<String> data,OnNoteListener onNoteListener){
        this.data = data;
        this.onNoteListener = onNoteListener;
    }
Enter fullscreen mode Exit fullscreen mode
  • However, I want to point out that just because we are referencing to it as an onNoteListener type, it is still technically our ExampleFragment class.

Passing onNoteListener to onCreateViewHolder()

  • Now inside the onCreateViewHolder() method we call the constructor of the ViewHolder object.
public CustomAdapter.ViewHolder onCreateViewHolder( ViewGroup parent, int viewType) {
        //inflate the indiv item
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.indiv_viewholder_item,parent,false);

        return new ViewHolder(view, this.onNoteListener);
    }
Enter fullscreen mode Exit fullscreen mode
  • This is still a normal onCreateViewHolder() method. However, notice that we are passing the global instance variable this.onNoteListener to the new ViewHolder() object. The ViewHolder instance will now get an inflated view as well as access to our onNoteClick method. Our viewHolder object now has access to the method that we defined earlier:
public void onNoteClick(int position) {
        Toast toast = Toast.makeText(getActivity(),"CLICKED",Toast.LENGTH_SHORT);
        toast.show();

    }
Enter fullscreen mode Exit fullscreen mode

Hooking up the ViewHolder object.

  • In order for us to define a onClickListener on our ViewHolder object we need to implements View.OnClickListener:
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
        private final TextView textView;
        OnNoteListener onNoteListener;

        public ViewHolder(View view,OnNoteListener onNoteListener){
            super(view);
            this.textView = (TextView) view.findViewById(R.id.text_view_id);
            view.setOnClickListener(this);
            this.onNoteListener = onNoteListener;
        }

        @Override
        public void onClick(View v) {
            this.onNoteListener.onNoteClick(getAdapterPosition());
        }
    }

Enter fullscreen mode Exit fullscreen mode
  • Notice the constructor and how we have an instance of onNoteListener and we are assigning it to a global reference variable inside of the ViewHolder class: this.onNoteListener = onNoteListener;. This will allow us to reference it later.

  • The most important part of this is the view.setOnClickListener(this), without this method your ViewHolder objects will not be clickable. this is referring to the actual ViewHolder object. It is basically us saying that when the individual view is clicked we want the onClick method to be called.

public void onClick(View v) {
            this.onNoteListener.onNoteClick(getAdapterPosition());
        }
Enter fullscreen mode Exit fullscreen mode
  • The code above is the last part of the puzzle. onClick() will get called whenever a ViewHolder object is called and with it we are calling our own method that we defined this.onNoteListener.onNoteClick(getAdapterPosition()) with the current position in the adapter.

  • That concludes how to make your RecyclerView clickable.

Conclusion

  • Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.

Top comments (0)