95

I'm writing my first app in Kotlin after 3 years of experience with Android. Just confused as to how to utilize itemClickListener with a RecyclerView in Kotlin.

I have tried the trait (edit: now interface) approach, very Java-like

public class MainActivity : ActionBarActivity() {

  protected override fun onCreate(savedInstanceState: Bundle?) {

    // set content view etc go above this line

    class itemClickListener : ItemClickListener {
      override fun onItemClick(view: View, position: Int) {
        Toast.makeText(this@MainActivity, "TEST: " + position, Toast.LENGTH_SHORT).show()
      }
    }

    val adapter = DrawerAdapter(itemClickListener())
    mRecyclerView.setAdapter(adapter)
 }

  trait ItemClickListener {
    fun onItemClick(view: View, position: Int)
  }
}

That seemed very redundant so I tried the inner class approach:

inner class ItemClickListener {
    fun onItemClick(view: View, position: Int) {
        startActivityFromFragmentForResult<SelectExerciseActivity>(SELECT_EXERCISES)
    }
}

And then just setting the adapter's click listener like this:

val adapter = WorkoutsAdapter(ItemClickListener())

But I'm still not satisfied with this because I think there might be a better, cleaner way. I'm trying to essentially achieve something like this: RecyclerView onClick

Any suggestions?

Ended up going with a variation of the approved answer

Defined the function in the activity:

val itemOnClick: (View, Int, Int) -> Unit = { view, position, type ->
    Log.d(TAG, "test")
}

Passed the function itself on to the adapter like this:

class ExercisesAdapter(val itemClickListener: (View, Int, Int) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
      // other stuff up here
      val vhExercise = ExerciseVH(view) // view holder
      // on to the view holder through the extension function
      vhExercise.onClick(itemClickListener)
    }
}

Extension function by Loop in the approved answer below.

fun <T : RecyclerView.ViewHolder> T.onClick(event: (view: View, position: Int, type: Int) -> Unit): T {
    itemView.setOnClickListener {
        event.invoke(it, getAdapterPosition(), getItemViewType())
    }
    return this
}
Afzal N
  • 2,546
  • 1
  • 26
  • 24
  • how you will call extension function onClick in your activity – YLS Nov 17 '17 at 09:36
  • You set the itemClickListener (itemOnClick in this example) in the constructor of the Adapter. The ViewHolder calls it. In hindsight, this still works and I should probably use this more lol. Keep making a manual onClick method every time these days. – Afzal N Jan 22 '18 at 22:29
  • Also you can take Rx or LiveData approaches to avoid using callbacks for passing around your events. – Dusk Nov 13 '18 at 09:30
  • I'm still struggling to get the onClickListener to work. For the line `val vhExercise = ExerciseVH(view)`, where does `ExerciseVH` come from? I don't see it anywhere in your answer. – N.Barrett Oct 27 '22 at 16:34
  • `ExerciseVH` is a ViewHolder subclass. – Afzal N Oct 30 '22 at 19:14
  • you can check this article for the detailed explanation https://androidacademic.blogspot.com/2023/03/recyclerview-with-itemclicklistener-kotlin.html – Pragnesh Ghoda シ Mar 14 '23 at 08:09

23 Answers23

138

My solution is like a combination of the previous ones with a super clean call from the activity.

ContactAdapter:

class ContactAdapter @Inject constructor() : RecyclerView.Adapter<ContactAdapter.ViewHolder>() {

    var onItemClick: ((Contact) -> Unit)? = null
    var contacts: List<Contact> = emptyList()

    ...

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val contact = contacts[position]

        holder.email.text = contact.email
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val email: TextView = itemView.email

        init {
            itemView.setOnClickListener {
                onItemClick?.invoke(contacts[adapterPosition])
            }
        }
    }
}

ContactActivity:

override fun setupRecyclerAdapter() {
    recyclerView.adapter = contactAdapter
    recyclerView.layoutManager = LinearLayoutManager(this)

    contactAdapter.onItemClick = { contact ->

        // do something with your item
        Log.d("TAG", contact.email)
    }
}
denwehrle
  • 1,844
  • 2
  • 15
  • 16
  • 3
    Oh nice, I like this one – Afzal N Mar 29 '18 at 22:24
  • Me too it solved an issue when i update UI thanks so much – Alex Rivas Jul 13 '20 at 18:11
  • 2
    Remember to clear the reference onDestroyView as this may leak. – loshkin Jul 16 '20 at 11:10
  • Thanks, @denwehrle. I love this style in iOS too. – gstream Mar 15 '21 at 10:06
  • 1
    What is this `override fun setupRecyclerAdapter`? From where does it override? – El_Loco Jun 05 '21 at 14:44
  • I have been looking for a way to replace interfaces with lambdas for a very long time. Thank you very much. If I could give 100000 upvotes, I would have. – Junior Aug 02 '21 at 13:41
  • I'm getting the "Unresolved reference: onItemClick" in my fragment. Any idea what I did wrong? Fragment: recyclerListaPotrosnje.adapter = StatistikaAdapter(popisIznosa) recyclerListaPotrosnje.layoutManager = LinearLayoutManager( requireContext(), LinearLayoutManager.VERTICAL, false ) StatistikaAdapter.onItemClick = { dataModel -> } – akrelj Aug 17 '21 at 11:57
43

I have a little bit different approach. You can create an extension for your ViewHolder

fun <T : RecyclerView.ViewHolder> T.listen(event: (position: Int, type: Int) -> Unit): T {
    itemView.setOnClickListener {
        event.invoke(getAdapterPosition(), getItemViewType())
    }
    return this
}

Then use it in adapter like this

class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    val items: MutableList<String> = arrayListOf()

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MyViewHolder? {
        val inflater = LayoutInflater.from(parent!!.getContext())
        val view = inflater.inflate(R.layout.item_view, parent, false)
        return MyViewHolder(view).listen { pos, type ->
            val item = items.get(pos)
            //TODO do other stuff here
        }
    }

    override fun onBindViewHolder(holder: MyViewHolder?, position: Int) {

    }

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


    class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {

    }
}

I am working with my colleagues on library providing such extensions.

Damian Petla
  • 8,963
  • 5
  • 44
  • 47
  • 13
    This is called in Adapter. How to use this in mainActivity as callback? – bpolat Sep 01 '17 at 12:08
  • @bpolat you can create a call back, for example, MyAdapter(private val itemClickedListener: (String) -> Unit), and call it in the listen function with itemClickedListener.invoke(item). In you activity, you instantiate adapter = MyAdapter { /*handle your cb here*/ } – pkliang Apr 11 '18 at 08:14
  • how to handle longOnClickListener in same way? – Jarvis Jul 28 '18 at 20:54
  • 1
    By this approach onClick method not every time is called. – Ponomarenko Oleh Jan 10 '19 at 15:30
  • I notice that if `OnClickListener` is added in method `onCreateViewHolder` then sometimes it doesn't work. So I suggest to add `OnClickListener` in method `onBindViewHolder` – NickUnuchek Apr 22 '21 at 06:42
24

In case anyone is looking for a more no-frills answer, I tried the following - which is very similar to the solution from AfzalivE:

In my Adapter class I passed the clickListener as a parameter. On onBindViewHolder, I've used setOnClickListener to call clickListener and handle click event.

MyAdapter.kt:

class MyAdapter constructor(objects: ArrayList<MyObject>, val clickListener: (MyObject) -> Unit) : RecyclerView.Adapter<MyAdapter.Holder>() {

    private var mObjects : ArrayList<MyObject> = ArrayList<MyObject>()

    init {
        mObjects = objects
    }

    override fun onBindViewHolder(holder: Holder?, position: Int) {
        var item : MyObject = objects[position]

        // Calling the clickListener sent by the constructor
        holder?.containerView?.setOnClickListener { clickListener(item) }
    }

    // More code (ViewHolder definitions and stuff)...

}

Note: I needed a reference from my list item's container (the root view), which in this case is containerView

Then I passed my object as parameter without need for searching it on a list again and handle it directly on my Activity class, in the moment I set the adapter:

MyActivity.kt:

myRecyclerView?.adapter = MyAdapter(mObjects) {
    Log.e("Activity", "Clicked on item ${it.itemName}")
}  

Update

If you need to get the position of the clicked item, just define it as parameter on the callback and then send it back later. Notice the val clickListener: (MyObject, Int) -> Unit below:

MyAdapter.kt

class MyAdapter constructor(objects: ArrayList<MyObject>, val clickListener: (MyObject, Int) -> Unit) : RecyclerView.Adapter<MyAdapter.Holder>() {
    // Rest of the code...

Then on onBindViewHolder() you pass the position when calling the callback method:

override fun onBindViewHolder(holder: Holder?, position: Int) {
    var item : MyObject = objects[position]

    // Calling the clickListener sent by the constructor
    holder?.containerView?.setOnClickListener { clickListener(item, position) }
}

And on MyActivity.kt, you'll have to change the way you set the adapter so you can get the position. Like this:

myRecyclerView?.adapter = MyAdapter(mObjects) { itemDto: MyObject, position: Int ->
        Log.e("MyActivity", "Clicked on item  ${itemDto.someItemPropertyLikeName} at position $position")
    }
sednanre
  • 478
  • 5
  • 16
19

Sorry for the delay, Got an awesome answer from this link and it was in Java.. Did some Homework and converted it to Kotlin..

Now it is working Properly.. Here is the Code,

Create a class named RecyclerItemClickListenr,

class RecyclerItemClickListenr(context: Context, recyclerView: RecyclerView, private val mListener: OnItemClickListener?) : RecyclerView.OnItemTouchListener {

private val mGestureDetector: GestureDetector

interface OnItemClickListener {
    fun onItemClick(view: View, position: Int)

    fun onItemLongClick(view: View?, position: Int)
}

init {

    mGestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
        override fun onSingleTapUp(e: MotionEvent): Boolean {
            return true
        }

        override fun onLongPress(e: MotionEvent) {
            val childView = recyclerView.findChildViewUnder(e.x, e.y)

            if (childView != null && mListener != null) {
                mListener.onItemLongClick(childView, recyclerView.getChildAdapterPosition(childView))
            }
        }
    })
}

override fun onInterceptTouchEvent(view: RecyclerView, e: MotionEvent): Boolean {
    val childView = view.findChildViewUnder(e.x, e.y)

    if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
        mListener.onItemClick(childView, view.getChildAdapterPosition(childView))
    }

    return false
}

override fun onTouchEvent(view: RecyclerView, motionEvent: MotionEvent) {}

override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}}

and access it from Activity/Fragment as

recyclerView.addOnItemTouchListener(RecyclerItemClickListenr(this, recyclerView, object : RecyclerItemClickListenr.OnItemClickListener {

        override fun onItemClick(view: View, position: Int) {
            //do your work here..
        }
        override fun onItemLongClick(view: View?, position: Int) {
            TODO("do nothing")
        }
    }))
Rajesh Naddy
  • 1,120
  • 12
  • 17
13

You can easily achieve this by using an interface

class ExercisesAdapter(private val itemClickListener: ItemClickListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    interface ItemClickListener {
        fun onItemClick(position: Int)
        fun onLongClick(position: Int)
    }

    inner class MyViewHolder(view:View): RecyclerView.ViewHolder(view){

        init {
            view.setOnClickListener {
                if (bindingAdapterPosition >= 0) {
                    itemClickListener.onItemClick(bindingAdapterPosition)
                }
            }

            view.setOnLongClickListener{
                if (bindingAdapterPosition >= 0) { 
                    itemClickListener.onLongClick(bindingAdapterPosition)
                }
                return@setOnLongClickListener true
            }
        }
    }
}

From your MainActivity

public class MainActivity : AppCompatActivity(), ExercisesAdapter.ItemClickListener {

   protected override fun onCreate(savedInstanceState: Bundle?) {

       // set content view etc go above this line
       mAdapter = ExercisesAdapter(this)
   }

   override fun onItemClick(position: Int) {
        Toast.makeText(this@MainActivity, "TEST: " + position, Toast.LENGTH_SHORT).show()
    }

    override fun onLongClick(position: Int) {
        //do long click here
    }
}
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
Anga
  • 2,450
  • 2
  • 24
  • 30
  • This is BY FAR the simplest, most elegant solution. I don't know WTF Google was thinking when they omitted onClickListener from RecyclerView, but based on the many CLOSED threads on StackOverflow, they REALLY SCREWED THIS UP. – Bungles Sep 26 '21 at 18:05
  • @Bungles no they didn't. They just didn't expect people to have so much trouble defining an interface that can expose click events to whoever creates the adapter. – EpicPandaForce Dec 27 '22 at 18:49
11

Add ClickListener code on onBindViewHolder fun

override fun onBindViewHolder(holder: ViewHolder, position: Int) {

    holder.vieww.textView.setText(arr.get(position))

    holder.vieww.setOnClickListener {(holder.vieww.textView.setTextColor(Color.GREEN))} // click event
}
iOS Lifee
  • 2,091
  • 23
  • 32
8

Slightly different, based on denwehrle

To use on a fragment, inside OnCreateView

 adapter.onItemClick = {it ->
    //do something
 }

Add in the adapter class:

var onItemClick: ((Contact)->Unit) ?= null
...

inner class contactViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
        val myItemView: TextView = itemView.findViewById(R.id.textView)

        init{
            itemView.setOnClickListener {
                onItemClick?.invoke(contact[adapterPosition])
            }
        }
}
Andrew
  • 26,706
  • 9
  • 85
  • 101
LiA
  • 356
  • 4
  • 5
7

Updated in 05 - 2019

I think the most elegant solution is to give this responsibility to recyclerView and not to view or even adapt it.

for that we need:

1: Create RecyclerItemClickListener file

class RecyclerItemClickListener(
        private val mRecycler: RecyclerView,
        private val clickListener: ((position: Int, view: View) -> Unit)? = null,
        private val longClickListener: ((position: Int, view: View) -> Unit)? = null
) : RecyclerView.OnChildAttachStateChangeListener {

    override fun onChildViewDetachedFromWindow(view: View) {
        view.setOnClickListener(null)
        view.setOnLongClickListener(null)
    }

    override fun onChildViewAttachedToWindow(view: View) {
        view.setOnClickListener { v -> setOnChildAttachedToWindow(v) }
    }

    private fun setOnChildAttachedToWindow(v: View?) {
        if (v != null) {
            val position = mRecycler.getChildLayoutPosition(v)
            if (position >= 0) {
                clickListener?.invoke(position, v)
                longClickListener?.invoke(position, v)
            }
        }
    }
}

2: Create/Add extensions for RecyclerView:

import android.support.v7.widget.RecyclerView
import com.app.manager.internal.common.RecyclerItemClickListener
                    
@JvmOverloads
fun RecyclerView.affectOnItemClicks(onClick: ((position: Int, view: View) -> Unit)? = null, onLongClick: ((position: Int, view: View) -> Unit)? = null) {
    this.addOnChildAttachStateChangeListener(RecyclerItemClickListener(this, onClick, onLongClick))
}

3: And finally the use (i suppose you use kotlinx)

import kotlinx.android.synthetic.main.{your_layout_name}.*

class FragmentName : Fragment() {

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        recycler.affectOnItemClick { position, view -> /*todo*/ }
    }
}
Community
  • 1
  • 1
Maxime Jallu
  • 2,352
  • 1
  • 10
  • 12
  • 1
    Would someone please elaborate on why this is a good solution? I’m new to Android development and this seems very verbose and boilerplate to me. – Bink Feb 01 '19 at 17:56
  • 2
    It's a solution, but your instincts are correct - there's too much boilerplate here. Though it takes a few passes to understand the kotlin syntax, a decent approach can be found here: https://antonioleiva.com/recyclerview-adapter-kotlin/ – 0xMatthewGroves Jun 18 '19 at 03:22
  • There is no reason to add about 40% of this code. – EpicPandaForce Dec 27 '22 at 18:51
7

You don't need to write extension function to ViewHolder or something like this.
Best practice; use Higher-Order Function

MainRecyclerAdapter

class MainRecyclerAdapter(val news: JSONArray, private val itemClickListener: (Int) -> Unit) : RecyclerView.Adapter<MainRecyclerAdapter.ViewHolder>() {}

Just add a Higher-order func. like itemClickListener and then go to the ViewHolder class. Write this function to your bind function as parameter and set this to itemView Like that :

MainRecyclerAdapter.ViewHolder

 class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {

        fun bind(newsItem: JSONObject,itemClickListener:(Int)->Unit) {
            //Some Stuff here..

            itemView.setOnClickListener { itemClickListener(adapterPosition) }

        }
    }

Use this method onBindViewHolder

OnBindViewHolder

 override fun onBindViewHolder(holder: MainRecyclerAdapter.ViewHolder, position: Int) {

        holder.bind(news.getJSONObject(position),itemClickListener)
    }

And now you can write your onClick function in any activity or fragments.. Just give as parameter.

Activity or Fragment

val itemOnClick: (Int) -> Unit = { position ->
                newsRecyclerView.adapter!!.notifyDataSetChanged()
                Toast.makeText(this.context,"$position. item clicked.",Toast.LENGTH_SHORT).show()
            }
 newsRecyclerView.adapter = MainRecyclerAdapter(news,itemClickListener = itemOnClick)
faskN
  • 704
  • 5
  • 4
7

3 easy steps:

1. pass in argument of your adapter as follow:

class ListAdapter(private val mListener: (ListItemDataClass) -> Unit)

2. in onBindViewHolder function, use like this

override fun onBindViewHolder(holder: YourViewHolder, position: Int) {

    val item = getItem(position)

    holder.itemView.setOnClickListener {
            item?.let { it1 -> mListener.invoke(it1) }
    }
}

3. and in your activity, use like this

val adapter = ListAdapter {
        Toast.makeText(this, it.title, Toast.LENGTH_SHORT).show()
}
Kishan Solanki
  • 13,761
  • 4
  • 85
  • 82
5

Here is a simple approach without using an interface, just in your adapter create an init block within viewholder class. Like below

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    init {
        itemView.setOnClickListener{
            //your code here---
        }
    }
}
Shivam Kumar
  • 1,892
  • 2
  • 21
  • 33
Siele Kim
  • 89
  • 2
  • 6
  • 3
    I wish people would stop answering this question. It doesn't need 24 answers. Especially seeing how old this question is, back when trait was a thing in Kotlin. – Afzal N Dec 10 '19 at 07:00
  • You must never use click listeners inside your Adapter! I repeat NEVER! – Kishan Solanki Aug 09 '21 at 03:44
  • @KishanSolanki why? it was literally intended to be used that way. As in, creating click listeners on the view. – EpicPandaForce Dec 27 '22 at 18:52
4

Adapter constructor declaration

class YourAdapter(private val mListener: (ItemObject) -> Unit) : RecyclerView.Adapter<ViewHolder>()

Adapter::onBindViewHolder

holder.itemView.setOnClickListener {
    mListener.invoke(item) // <- item instance of ItemObject
}

How to Use

mTypesWasteAdapter = YourAdapter({ it.something()})

Basically, you will receive the ItemObject as it in the lambda argument.

3

Finally, here is a nice working solution:

MyRecyclerAdapter.kt

class MyRecyclerAdapter(val context: Context, val items : ArrayList<Item>, val clickListener: (Int) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, p1: Int): RecyclerView.ViewHolder {
        return MyViewHolder(LayoutInflater.from(context).inflate(R.layout.my_item, parent, false))
    }

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

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as MyViewHolder).clickableView.setOnClickListener {
            clickListener(position)
        }
    }
}

class MyViewHolder (view: View) : RecyclerView.ViewHolder(view) {
    val clickableView = view.clickable_view
}

MainActivity.kt

fun appClickListener(position: Int) {
    // You got the position of ArrayList
}

my_recyclerview.adapter = MyRecyclerAdapter(this, myList, clickListener = {
    appClickListener(it)
})
Ashwin
  • 7,277
  • 1
  • 48
  • 70
3

My simple solution using higher-order function and let scoping function to set listener only if itemAction has been set

// Adapter
private var itemAction: ((Item) -> Unit)? = null

fun setItemAction(action: (Item) -> Unit) {
    this.itemAction = action
}

inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    fun setItem(item: Item) {

        // ...

        itemAction?.let {
            itemView.setOnClickListener { it(item) }
        }
    }
}

and in activity/fragment

adapter.setItemAction { // <- it is Item
  // do something with it
}
mustofa.id
  • 169
  • 1
  • 13
  • As like java interface previously used for click listener. I wanted this solution for kotlin, Thanks for the code. – Touhid Sep 01 '19 at 04:56
2

You could try something like:

public class MainActivity : ActionBarActivity() {
    protected override fun onCreate(savedInstanceState: Bundle?) {
        [...]
        val adapter = DrawAdapter(::onItemClick)
        [...]
    }
}

fun onItemClick(view: View, position: Int) {
    //Do work
}

and SAM convertion just works like in Java 8, so just use a lambda:

public class MainActivity : ActionBarActivity() {
    protected override fun onCreate(savedInstanceState: Bundle?) {
        [...]
        val adapter = DrawAdapter({view, position -> /*Do work*/ })
        [...]
    }
}
D3xter
  • 6,165
  • 1
  • 15
  • 13
2

In RecyclerView you can put click on inflated view inside ViewHolder class and call it from onBindViewHolder callback method for example:

class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {

    val view = view
    val tv_message = view.tv_message
    val tv_address = view.tv_address

    fun bind(listViewItem: ListViewItem) {
        view.setOnClickListener(View.OnClickListener {

            Toast.makeText(
                view.context, 
                "Name: " + listViewItem.name + "/n Address: " + listViewItem.address, 
                Toast.LENGTH_LONG).show()
            })
        }
    }
}

You can call from adapter onBindViewHolder() method:

override fun onBindViewHolder(holder: ViewHolder, position: Int) {

    val listViewItem: ListViewItem = mListViewItems[position]
    holder.tv_message.text = listViewItem.name
    holder.tv_address.text = listViewItem.address
    holder.bind(mListViewItems[position]);
}
phen0menon
  • 2,354
  • 2
  • 17
  • 36
rakesh rajput
  • 606
  • 4
  • 5
2

//Step 1 make an interface like

interface RecyclerViewClickListener {
    fun onItemClick(position: String)
    fun onLongClick(position: Int)
}

Step 2 Inside Adapter class pass one more argument as an interface like

class ModelAdapter(var item_list: ArrayList<UploadDocument>,var mItemClickListener:RecyclerViewClickListener) : RecyclerView.Adapter<ModelAdapter.ViewHolder>() {


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModelAdapter.ViewHolder {
    // create a new view

    val view = LayoutInflater.from(parent.context).inflate(R.layout.upload_document_row_item, null)

    // create ViewHolder

    return ViewHolder(view)
}

override fun onBindViewHolder(holder: ModelAdapter.ViewHolder, position: Int) {

    holder.txtRegistrationDoc?.setText(item_list[position].getdocName())
    holder.txtCertificate?.setText(item_list[position].getcertificateName())
    holder.txtFileSize?.setText(item_list[position].getfileSize())
    holder.txtCreatedOn?.setText(item_list[position].getcreatedOn())
    holder.txtModifiedOn?.setText(item_list[position].getModifiedDate())

    //holder.chkSelected.isChecked = item_list[position].isSelected()

    holder.chkSelected.tag = item_list[position].getdocName()


        holder. chkSelected!!.setOnCheckedChangeListener { buttonView, isChecked ->

            if(isChecked)
            {
                System.out.println("position>>>"+buttonView.tag.toString())
                mItemClickListener.onItemClick(buttonView.tag.toString())
            }

        }

    //(context as UploadDocumentActivity::class.java).onClickCalled("your argument here")

   /* holder.btn_delete.setOnClickListener(object : View.OnClickListener() {
        override fun onClick(v: View) {

            deleteItemFromList(v, position)


        }
    })*/

}

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


/*// confirmation dialog box to delete an unit
private fun deleteItemFromList(v: View, position: Int) {

    val builder = AlertDialog.Builder(v.getContext())

    //builder.setTitle("Dlete ");
    builder.setMessage("Delete Item ?")
        .setCancelable(false)
        .setPositiveButton("CONFIRM",
            DialogInterface.OnClickListener { dialog, id ->
                item_list.remove(position)
                notifyDataSetChanged()
            })
        .setNegativeButton("CANCEL", DialogInterface.OnClickListener { dialog, id -> })

    builder.show()

}*/


class ViewHolder(
    itemLayoutView: View) : RecyclerView.ViewHolder(itemLayoutView) {

    var item_name: TextView
    var txtRegistrationDoc: TextViewNormal?=null
    var txtCertificate: TextViewNormal?=null
    var txtFileSize: TextViewNormal?=null
    var txtCreatedOn: TextViewNormal?=null
    var txtModifiedOn: TextViewNormal?=null
    var chkSelected: CheckBox


    init {

        item_name = itemLayoutView.findViewById(R.id.txt_Name)
        txtRegistrationDoc = itemLayoutView.findViewById(R.id.txtRegistrationDoc)
        txtCertificate = itemLayoutView.findViewById(R.id.txtCertificate)
        txtFileSize = itemLayoutView.findViewById(R.id.txtFileSize)
        txtCreatedOn = itemLayoutView.findViewById(R.id.txtCreatedOn)
        txtModifiedOn = itemLayoutView.findViewById(R.id.txtModifiedOn)
        //btn_delete = itemLayoutView.findViewById(R.id.btn_delete_unit)
        chkSelected = itemLayoutView.findViewById(R.id.chk_selected)


    }



}
}

//Step 3 Inside your activity/ Frgament

recyclerView?.adapter = ModelAdapter(documentList,object : `in`.mobilepedia.com.gicgwaliarincubationcentre.RecyclerViewClickListener
        {
            override fun onItemClick(position: String) {

            System.out.println("Position>>>>>"+position)
        }

        override fun onLongClick(position: Int) {

        }

    })
Adrita Sharma
  • 21,581
  • 10
  • 69
  • 79
1

Kotlin

Make your adapter constructor like this

    class ViewAdapter(
        private val context: Context,
        private val mListener: (DataClass) -> Unit
    ) :
        RecyclerView.Adapter<WeekRecyclerViewAdapter.ViewHolder>() {

// Your adapter code goes here

}

In your onBindViewHolder,

holder.binding.parentLayout.setOnClickListener {
                mListener.invoke(items[position]) // <- item instance of ItemObject
        }

In your Fragment, implement like below

class YourFragment : Fragment(), (DataClass) -> Unit {
    override fun invoke(p1: DataClass) {

        //You will get the selected item here

    }
Kanagalingam
  • 2,096
  • 5
  • 23
  • 40
0

Here is my MainActivity.kt class that uses recyclerview to populate location data. It has a simple on item click listener interface that you can implement.

    class MainActivity : AppCompatActivity() {

        private lateinit var recyclerView: RecyclerView
        private lateinit var viewAdapter: RecyclerView.Adapter<*>
        private lateinit var viewManager: RecyclerView.LayoutManager
        private var locationArrayList = arrayListOf<Location>()

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)

            //create locations
            var ny = Location("New York")
            var la = Location("Los Angeles")
            locationArrayList.addAll(listOf(ny, la))

            viewManager = LinearLayoutManager(this)
            viewAdapter = LocationsAdapter(locationArrayList)

            recyclerView = findViewById<RecyclerView>(R.id.recyclerView).apply {
                // use this setting to improve performance if you know that changes
                // in content do not change the layout size of the RecyclerView
                setHasFixedSize(true)

                // use a linear layout manager
                layoutManager = viewManager

                // specify an viewAdapter 
                adapter = viewAdapter

            }

    //recycler view click listener implement
            recyclerView.addOnItemClickListener(object: OnItemClickListener {
                override fun onItemClicked(position: Int, view: View) {
                    // Your logic
                    Toast.makeText(this@MainActivity, locationArrayList[position].locationName, Toast.LENGTH_SHORT).show()
                }
            })

        }

    //on item click interface
        interface OnItemClickListener {
            fun onItemClicked(position: Int, view: View)
        }

        fun RecyclerView.addOnItemClickListener(onClickListener: OnItemClickListener) {
            this.addOnChildAttachStateChangeListener(object: RecyclerView.OnChildAttachStateChangeListener {
                override fun onChildViewDetachedFromWindow(view: View?) {
                    view?.setOnClickListener(null)
                }

                override fun onChildViewAttachedToWindow(view: View?) {
                    view?.setOnClickListener({
                        val holder = getChildViewHolder(view)
                        onClickListener.onItemClicked(holder.adapterPosition, view)
                    })
                }
            })
        }
//end of interface
    }
Ronny K
  • 3,641
  • 4
  • 33
  • 43
0

If someone interested in the old way implementation..

I posted full example which also reduces your adapter code as well. It uses the old pattern of getting callback..

Project level gradle

buildscript {
    ext.kotlin_version = '1.3.10'
    repositories {
        google()
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"//newly added

        classpath 'com.google.gms:google-services:4.1.0' // google-services plugin

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

App level Gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'org.jetbrains.kotlin.android.extensions'//it is used for @Percelize

android {
    compileSdkVersion 28
    dataBinding {
        enabled = true
    }
    androidExtensions {
        experimental = true
    }
    defaultConfig {
        applicationId 'broadpeak.firebase.learning'
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
    }
}

/*kapt {
    generateStubs = true
}*/
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.google.firebase:firebase-core:16.0.5'
    implementation 'com.google.firebase:firebase-firestore:17.1.3'
    implementation 'com.google.firebase:firebase-auth:16.0.5'
    implementation 'com.google.firebase:firebase-messaging:17.3.4'
    implementation 'com.google.code.gson:gson:2.8.5'

    implementation 'com.firebaseui:firebase-ui-auth:4.1.0'

    implementation 'com.github.bumptech.glide:glide:4.8.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
    ////kapt "com.android.databinding:compiler:$android_plugin_version"\ // not required above 3.2.0
    ///kapt "com.android.databinding:compiler:3.1.4"
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'

SubjectListActivity.class

class SubjectListActivity : BaseActivity() {

    var subjects = mutableListOf<SubjectBO>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.question_list_activity)

        recycler_view.itemAnimator = DefaultItemAnimator()
        recycler_view.setHasFixedSize(true)
        recycler_view.layoutManager = LinearLayoutManager(this@SubjectListActivity)

        db.collection("tagCollection").get().addOnSuccessListener { querySnapshot ->
            if (querySnapshot.isEmpty()) {
                Log.d(TAG, "onSuccess: LIST EMPTY")
            } else {
                // Convert the whole Query Snapshot to a list
                // of objects directly! No need to fetch each document.
                subjects = querySnapshot.toObjects(SubjectBO::class.java)

                if(subjects.size > 0){
                    recycler_view.adapter = SubjectAdapter(subjects, object : OnRecyclerItemClickListener {
                        override fun onItemClicked(view: View?, position: Int) {
                            var intent = Intent(this@SubjectListActivity,McqActivity::class.java)
                            intent.putExtra("keyTagBO",subjects.get(position))
                            startActivity(intent)
                        }
                    });
                }

            }
        }.addOnFailureListener { exception ->
            exception.printStackTrace()
        }
    }

SubjectAdapter.class

class SubjectAdapter(items: List<SubjectBO>, onRecyclerItemClickListener: OnRecyclerItemClickListener)
    : BaseAdapter<SubjectBO, SubjectViewHolder>(items, onRecyclerItemClickListener) {

    override fun onCreateViewHolder(parent: ViewGroup, p1: Int): SubjectViewHolder {
        return SubjectViewHolder(parent, R.layout.item_subject, onRecyclerItemClickListener)
    }
}

SubjectViewHolder.class

class SubjectViewHolder(parent: ViewGroup, itemLayoutId: Int, onRecyclerItemClickListener:
    OnRecyclerItemClickListener) : BaseViewHolder<SubjectBO>(parent, itemLayoutId, onRecyclerItemClickListener) {

    override fun bindData(data: SubjectBO) {
        itemView.tvTitle.setText(data.tagName)
    }
}

BaseAdapter.class

abstract class BaseAdapter<T, U : BaseViewHolder<T>>
(var items: List<T>, var onRecyclerItemClickListener: OnRecyclerItemClickListener)
    : RecyclerView.Adapter<U>() {

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

    override fun onBindViewHolder(holder: U, pos: Int) {
        holder.bindData(items.get(pos))
    }
}

BaseViewHolder.class

abstract class BaseViewHolder<T : BaseModel>(parent: ViewGroup, @LayoutRes itemLayoutId: Int,
                                             var onRecyclerItemClickListener: OnRecyclerItemClickListener) :
        RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(itemLayoutId, parent,
                false)), View.OnClickListener {

    override fun onClick(v: View?) {
        onRecyclerItemClickListener.onItemClicked(v, adapterPosition)
    }

    abstract fun bindData(data: T)

    init {
        itemView.setOnClickListener(this)
    }
}

OnRecyclerItemClickListener.class

interface OnRecyclerItemClickListener{
    fun onItemClicked(view: View?, position: Int)
}
Zar E Ahmer
  • 33,936
  • 20
  • 234
  • 300
-1

Two ways you can have an access to recyclerview in kotlin is first you can directly declare OnClickListener in adapter and make redirection inside it and second way is you can redirect your onclick to fragment/activity where you have set adapter of recycler

1.

 class CartAdapter(context: Context) : RecyclerView.Adapter<CartAdapter.ViewHolder>() {
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_cart, parent, false));
        }

        override fun getItemCount(): Int {
            return 10;
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
           holder.itemView.rtbProductRating.setOnClickListener{

            var iNavigation= Intent(context,MainActivity::class.java)
            iNavigation.flags= Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
            context.startActivity(iNavigation)

// directly redirect your activity from adapter
           }

        }

        class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView)
    } 

Second way you can have is redirect your adapter click to fragment/activity and then redirect your activity from there instead of redirecting from adapter

 class CartAdapter(context: Context, onClickListener: View.OnClickListener) : RecyclerView.Adapter<CartAdapter.ViewHolder>() {
        var context = context
        var onClickListener = onClickListener
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.layout_cart, parent, false));
        }

        override fun getItemCount(): Int {
            return 10;
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {

//set your position to the view
            holder.itemView.rtbProductRating.tag = position

//redirect click to the fragment
            holder.itemView.rtbProductRating.setOnClickListener {
                onClickListener.onClick(holder.itemView.rtbProductRating)

            }
    //        holder.itemView.tv_waybill_count.text = holder.itemView.context.getString(R.string.waybills,5)
        }

        class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView)
    }


Your fragment will look like:

class CartFragment: BaseFragment(),View.OnClickListener {
    override val layout= R.layout.frg_cart

     override fun onClick(v: View?) {
      var position=v?.tag as Int

        if(position==0){
            var iNavigation= Intent(this,MainActivity::class.java)
            iNavigation.flag=Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
            startActivity(iNavigation)
        }
    }

  override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        listener()
    }

    private fun listener() {
        cart_rv.adapter=CartAdapter(activity,this)
    }
}
Android Geek
  • 596
  • 8
  • 14
-1

OH what the heck someone might like this We all place edit and trashcan images in out recyclerview and would like something to happen when they are clicked. Here is our Kotlin example

This is in a card view that is inflated in the Adapter

    <RelativeLayout
    android:id="@+id/editCLICK"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:layout_marginStart="370dp"
    android:paddingLeft="6dp"
    android:paddingRight="6dp"
    android:paddingTop="12dp">

    <ImageView
        android:id="@+id/ivEdit"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:background="@color/color_Transparent"
        android:src="@drawable/ic_edit"
        android:tint="@color/color_lightBlue" />

</RelativeLayout>

then in the Adapter we do some binding

    override fun onBindViewHolder(holder: ParentViewHolder, position: Int) {
    val items = parentList[position]
    holder.item.text = items.dept


    holder.editCLICK.setOnClickListener {
        val i = Intent(context, EnterParentActivity::class.java)
        i.putExtra("FROM", "U")
        i.putExtra("MainActId",items.idD)
        i.putExtra("PFK",items.fkD)
        i.putExtra("ET",items.dept)
        i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        context.startActivity(i)
    }
}


inner class ParentViewHolder(view: View):RecyclerView.ViewHolder(view){
    var item: TextView = view.findViewById(R.id.tvDept) as TextView
    var editCLICK: RelativeLayout = view.findViewById(R.id.editCLICK) as RelativeLayout
}

Simple quick and reliable enjoy

Vector
  • 3,066
  • 5
  • 27
  • 54
-1

I came up with this solution to open an activity when row is clicked, using a companion object and interface. The activity is opened from main activity since I had to save list state before leaving.

Adapter

class MyAdapter(
    val dataList: List<objects.ListObject>, val listener: ItemClickListener
) : RecyclerView.Adapter<MyAdapter.ListViewHolder>()
{

    companion object {
        var mClickListener: ItemClickListener? = null
    }

    interface ItemClickListener
    {
        fun clickRow(position: Int)
    }

    override fun onBindViewHolder(holder: MyAdapter.ListViewHolder, position: Int)
    {
        holder.bindData(
            ...
        )

        mClickListener = listener
        holder.itemView.setOnClickListener { view ->

            mClickListener?.clickRow(position)
        }
    }

    ... 
}

Main activity

val context = this
private lateinit var mMyAdapter: MyAdapter

fun initList()
{
    mMyAdapter =
        MyAdapter(dataList, object : MyAdapter.ItemClickListener
        {
            override fun clickRow(position: Int)
            {
                openActivityListItems(position)
            }
        }
    )
}

fun openActivityListItems(position : Int)
{
    recyclerViewState = mListView.getLayoutManager()?.onSaveInstanceState()

    val intent = Intent(context, ListItems::class.java)
    intent.putExtra("Parameter1", dataList[position].Parameter1)
    intent.putExtra("Parameter2", dataList[position].Parameter2)
    context.startActivity(intent)
}