'Problem with RecyclerView OnClick (kotlin)

sorry for the inconvenience, but this is my first time trying this. I've been trying on doing this app which it has a recyclerView which I intent it to open a chrome page. The thing is, I'm not able to implement the itemOnClickListener despite having tried a lot of videos. Could you point out where is my mistake?

My Adapter:

package com.example.practica1.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.example.practica1.R
import com.example.practica1.data.Video

class MainAdapter(private val context: Context, private val videos: List<Video>): RecyclerView.Adapter<MainAdapter.ViewHolder>(){

    private lateinit var mListener: onItemClickListener

    interface onItemClickListener{
        fun onItemClick(position: Int)
    }

    fun setOnClickListener(listener: onItemClickListener){
        mListener = listener
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainAdapter.ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_cartoon, parent, false)
        return ViewHolder(view, mListener)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val video = videos[position]
        holder.title.text = video.data.short_title
        holder.serieTitle.text = video.data.show.title
        holder.durationTitle.text = video.data.video.duration + " seg"
        Glide.with(context)
            .load(video.data.imagepath)
            .diskCacheStrategy(DiskCacheStrategy.ALL)
            .override(400, 400)
            .centerCrop()
            .into(holder.imagePrev)

    }

    override fun getItemCount() = videos.size

    inner class ViewHolder(itemView: View, listener: onItemClickListener) : RecyclerView.ViewHolder(itemView){
        val title: TextView = itemView.findViewById(R.id.videoTitle)
        val serieTitle: TextView = itemView.findViewById(R.id.serieTitle)
        val durationTitle: TextView = itemView.findViewById(R.id.durationTitle)
        val imagePrev: ImageView = itemView.findViewById(R.id.photoPreview)

        init{
            itemView.setOnClickListener{
                listener.onItemClick(adapterPosition)
            }
        }
    }

}

The fragment in which I tried to implement the method:

package com.example.practica1.fragments

import android.os.Bundle
import android.util.Log
import android.util.Log.d
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import com.example.practica1.R
import com.example.practica1.adapter.MainAdapter
import com.example.practica1.data.ApiInterface
import com.example.practica1.data.Video
import com.example.practica1.databinding.FragmentMenuBinding
import com.haerul.bottomfluxdialog.BottomFluxDialog
import com.haerul.bottomfluxdialog.BottomFluxDialog.OnInputListener
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MenuFragment : Fragment() {
    private var _binding: FragmentMenuBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        _binding = FragmentMenuBinding.inflate(inflater, container, false)

        val view = inflater.inflate(R.layout.fragment_menu, container, false)

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val retrofit = Retrofit.Builder()
            .baseUrl("https://www.cartoonnetwork.com.co")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val api = retrofit.create(ApiInterface::class.java)
        showProgress()
        api.fetchAllVideos().enqueue(object : Callback<List<Video>> {
            override fun onResponse(call: Call<List<Video>>, response: Response<List<Video>>) {
                if (response.isSuccessful()) {
                    d("daniel", "onResponse ${response.body()!![0].id}")
                    if (response.body()!!.size > 0) {
                        showData(response.body()!!)
                        getResult(true, "Sucess")
                    } else
                        getResult(false, response.message())

                    hideProgress()
                }

            }

            override fun onFailure(call: Call<List<Video>>, t: Throwable) {
                d("daniel", "onFailure")
                getResult(false, "Request Timeout. Please Try Again!")
                hideProgress()
            }

        })

        binding.search.setOnClickListener { v -> showDialog(v) }
    }

    fun showData(videos: List<Video>){
        val test: RecyclerView = requireActivity().findViewById(R.id.recycler_viewMenu)
        var adap = MainAdapter(requireContext(), videos)
        test.apply{
            adapter = adap
        }

        adap.setOnClickListener(object: MainAdapter.onItemClickListener{
            override fun onItemClick(position: Int){
                d("test", "test")
            }
        })
    }

    fun showProgress(){
        binding.swipeRefresh.setRefreshing(true)
        binding.emptyView.setVisibility(View.GONE)
        binding.shimmer.startShimmer()
        binding.shimmer.setVisibility(View.VISIBLE)
        binding.recyclerViewMenu.setVisibility(View.GONE)
    }

     fun hideProgress(){
        binding.swipeRefresh.setRefreshing(false)
        binding.shimmer.stopShimmer()
        binding.shimmer.setVisibility(View.GONE)
        binding.recyclerViewMenu.setVisibility(View.VISIBLE)
    }

    fun getResult(status: Boolean, message: String){
        if(!status) {
            binding.emptyView.setVisibility(View.VISIBLE)
            binding.textEmptyErr.setText(message)
        }
        else
            binding.emptyView.setVisibility((View.GONE))
    }

    private fun showDialog(view: View){
        BottomFluxDialog.inputDialog(requireActivity())
            .setTextTitle("Input Title")
            .setTextMessage("This is an input message")
            .setRightButtonText("SUBMIT")
            .setInputListener(object : OnInputListener {
                override fun onSubmitInput(text: String?) {
                    val bundle = Bundle()
                    bundle.putString("search", text)
                    Navigation.findNavController(view)
                        .navigate(R.id.action_menuFragment_to_listFragment, bundle)
                }

                override fun onCancelInput() {}

            })

            .show()
    }


}

Thanks!



Solution 1:[1]

There is no such common thin like OnItemClickListener for RecyclerView, so you have to design your own. It shouldn't be hard to implement by hand, though. What you can do, is that you can Just set OnClickListener to the whole item view and delegate it clicks to upper lambda.

class YourAdapter(
    private val listener: (Video) -> Unit,
    private val videos: List<Video>,
): ... { 
     ...
     override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        ...
        holder.itemView.setOnClickListener { v -> 
            if (position != RecyclerView.NO_POSITION) {
                listener(videos[position])
            }
        }
     }
}

There are no noticeable side-effects of swapping View.OnClickListener in onBindViewHolder() method.

Also, few suggestions:

  1. Read about ListAdapterhere. Looks like it perfect fit for your usecase.
  2. Use getAbsoluteAdapterPosition() instead of adapterPosition() method within ViewHolder, as adapters are designed to be combined. Read more about ConcatAdapter here.
  3. You don't need to pass Context to adapter constructor. View already has context, so you can query context by calling viewHolder.itemView.context.
  4. Make sure to use your binding. It looks like you creating your binding in MenuFragment, but later you are getting RecyclerView by id in an old-fashioned way.
  5. Don't create adapters, that require data for creation. Instead, pass and update data as you requested from the backend. RecyclerView.Adapter is designed to be reused.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1