Search code examples
androidandroid-recyclerviewfragmentonclicklistener

How to add an onClickListener in RecyclerView using data binding


I just started using databinding now I'm stuck implementing a click listener the databinding way in a recyclerView. I have it working like this:

  @NonNull
    @Override
    public BookViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemBookCoversBinding binding = ItemBookCoversBinding.inflate(LayoutInflater.from(context), parent, false);
        final BookViewHolder bookViewHolder = new BookViewHolder(binding);
        bookViewHolder.binding.bookDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.deleteCover(bookViewHolder.getAdapterPosition());
            }
        });
        bookViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.changeCover(bookViewHolder.getAdapterPosition());
            }
        });
        return bookViewHolder;
    }

which literally works fine but I'm not using databinding for the clickListener. I have tried adding a variable position to my layout like this:

   <data>

        <variable
            name="position"
            type="int" />

        <variable
            name="listener"
            type="....interfaces.Listener" />


    </data>
    <ImageView
        android:id="@+id/book_cover"
        coverImage="@{bookImages.storageRef}"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/holo_green_dark"
        android:onClick="@{() -> listener.changeCover(position)}"
        android:src="@drawable/placeholder" />

and in the onCreateViewHolder in Adapter this:

bookViewHolder.binding.setVariable(BR.position, bookViewHolder.getAdapterPosition());

or this:

 binding.setPosition(bookViewHolder.getAdapterPosition());

The onClick works as expected on both cases but the bookViewHolder.getAdapterPosition() returns -1,NO_POSITION

What am I missing here? What is the best way to implement a databinding onClickListener in a RecyclerView?

I have already something that works, but is not the databinding way.


Solution

  • It does not work because you assign variables at view creation time. At this moment your ViewHolders don't have adapterPositions. I don't know how exactly they work, but whenever I tried to use it outside of click listeners, it always returned -1. They are not attached to the RecyclerView at this moment, so you won't get it without some asynchronous work or layoutChangeListeners and it will probably always be -1 in onCreateViewHolder

    The solution is to take your ViewHolder's position from other place. You can get this with viewType variable after overriding getItemViewType(int pos) method like that:

        @Override
        public int getItemViewType(int position) {
            return position;
        }
    
        @NonNull
        @Override
        public PlaylistViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewTypeButActuallyItIsPosition) {
            ...
            binding.setPosition(viewTypeButActuallyItIsPosition);
            ...
        }
    

    Edit

    One problem that you can face with such approach (and it seems to me you probably will) is that ImageViews will have wrong positions set when your ViewHolders have been recycled, as onBindViewHolder methods will be called without calling onCreateViewHolder, UI will be changed but the binding variable will stay the same, so the correct way would be assigning this position in onBindViewHolder method. This means you should keep your binding in a ViewHolder class and keep it public so you can access it in onBindViewHolder

        @Override
        public void onBindViewHolder(@NonNull YourViewHolder holder, int i) {
            ...
            holder.binding.setPosition(i);
        }