Search code examples
kotlinurlandroid-recyclerviewhyperlinkfragment

How do I open an URL from a RecyclerView in a Fragment in Kotlin?


I have a list of items in a RecyclerView that is in a Fragment, and I am trying to make it open an URL by clicking on a certain item. For example, if I click on the first item, it should redirect me to url1, but if I click on the second, it should redirect to url2.

I have seen solutions for recyclerviews in main activity, but those don't seem work in Fragments.

This is DietAdapter.kt

class DietAdapter(val dietList: List<Diet>): RecyclerView.Adapter<DietAdapter.DietViewHolder>(){

inner class DietViewHolder(val itemBinding: ListitemBinding)
    :RecyclerView.ViewHolder(itemBinding.root) {
    fun bindItem(diet:Diet){
        itemBinding.titleImage.setImageResource(diet.titleImage)
        itemBinding.tvHeading.text = diet.heading
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DietViewHolder {
    return DietViewHolder(ListitemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}

override fun onBindViewHolder(holder: DietViewHolder, position: Int) {
    val diet = dietList[position]
    holder.bindItem(diet)

    holder.itemBinding.rowList.setOnClickListener(View.OnClickListener {
        val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("<add url here>"))
        requireContext().startActivity(browserIntent) //<--here the requireContext throws "no value passed for parameter 'provider' "
    })

}
override fun getItemCount(): Int {
    return dietList.size
}

}


Solution

  • The quick and dirty solution:

    Add a Context property to your constructor:

    class DietAdapter(val context: Context, val dietList: List<Diet>) //...
    

    and when you create the adapter in your fragment, pass requireContext() as the parameter for context. Now you can use context.startActivity() inside your adapter's click listener(s).


    The solution with proper separation of concerns:

    Your adapter should not be internally responsible for navigation. That is a job for the higher level object, the Fragment. Trying to have the adapter reach into the Fragment to do things that a Fragment should do makes your code more convoluted. Instead, the adapter should expose a listener property (changes from your code are commented):

    class DietAdapter(
        val dietList: List<Diet>,
        var onItemClick: (Diet)->Unit = {}  // Added this
    ): RecyclerView.Adapter<DietAdapter.DietViewHolder>(){
        
        inner class DietViewHolder(val itemBinding: ListitemBinding)
            :RecyclerView.ViewHolder(itemBinding.root) {
            fun bindItem(diet:Diet){
                itemBinding.titleImage.setImageResource(diet.titleImage)
                itemBinding.tvHeading.text = diet.heading
            }
        }
        
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DietViewHolder {
            return DietViewHolder(ListitemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        }
        
        override fun onBindViewHolder(holder: DietViewHolder, position: Int) {
            val diet = dietList[position]
            holder.bindItem(diet)
        
            // Changed this:
            holder.itemBinding.rowList.setOnClickListener { onItemClick(diet) }
        
        }
        override fun getItemCount(): Int {
            return dietList.size
        }
    
    }
    

    Then, the fragment is where you decide what to do with clicked items:

    val dietAdapter = DietAdapter(theDataList) { diet ->
        val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("<add url here>"))
        requireContext().startActivity(browserIntent) 
    }
    binding.myRecyclerView.adapter = dietAdapter