Reputation: 1335
I'm trying to implement a click listener for a single recycler view item and run method from my activity with clicked data (single borrower).
How to deal with it? I'm new in kotlin, so step-by-step instruction will be very helpful :)
Thanks!
MainActivity:
val db by lazy { Database.getInstance(applicationContext).database }
fun startDebtActivity() {
val intent = Intent(this, AddDebtActivity::class.java)
startActivity(intent)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addDebtBtn.setOnClickListener {
startDebtActivity()
}
thread {
var borrowers = db.borrowers()
.getAll()
runOnUiThread {
val debtsSum = borrowers.sumByDouble { it.debt }
// update sum of debts
summary.text = debtsSum?.toBigDecimal().setScale(2, 2).toString()
// update item list
recycler.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = BorrowersListAdapter(borrowers)
}
}
}
}
Adapter:
class BorrowersListVh(view: View): RecyclerView.ViewHolder(view) {
fun refreshData(borrower: Borrower) {
itemView.borrowersListName.text = borrower.name
itemView.borrowersLisDebt.text = borrower.debt.toString()
itemView.setOnClickListener {
Log.e("info", "Clicked")
}
}
public interface BorrowerClickCallbacks {
fun onItemClick(position: Int)
}
}
class BorrowersListAdapter(private val dane: List<Borrower>) : RecyclerView.Adapter<BorrowersListVh>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BorrowersListVh {
return BorrowersListVh (
LayoutInflater.from(parent.context).inflate(R.layout.borrowers_list_item, parent,false)
)
}
override fun getItemCount(): Int = dane.size
override fun onBindViewHolder(holder: BorrowersListVh, position: Int) {
val borrower: Borrower = dane[position]
holder.refreshData(borrower)
// add notify
}
}
Upvotes: 2
Views: 8255
Reputation: 93581
Give your adapter constructor an additional property for a callback that can respond to a Borrower being clicked:
class BorrowersListAdapter(private val dane: List<Borrower>, val onItemClicked: (Borrower) -> Unit) //...
Then when binding views, you can set a listener on the item view to call the callback with the newly associated Borrower:
override fun onBindViewHolder(holder: BorrowersListVh, position: Int) {
val borrower: Borrower = dane[position]
holder.refreshData(borrower)
holder.itemView.setOnClickListener {
onItemClicked(borrower)
}
}
Then in your Activity, you can create a function that matches the callback function signature. The name doesn't matter.
fun onBorrowerClick(borrower: Borrower) {
Log.i("borrower clicked", borrower.toString())
}
And when you instantiate the adapter, pass this function as the callback to the constructor.
adapter = BorrowersListAdapter(borrowers, ::onBorrowerClick)
Side note, instead of using a thread directly like you are, you should use the lifecycleScope
to launch a coroutine so you aren't leaking your Activity to a background thread:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
addDebtBtn.setOnClickListener {
startDebtActivity()
}
lifecycleScope.launch {
val borrowers = withContext(Dispatchers.IO) {
db.borrowers()
.getAll()
}
val debtsSum = borrowers.sumByDouble { it.debt }
// update sum of debts
summary.text = debtsSum?.toBigDecimal().setScale(2, 2).toString()
// update item list
recycler.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = BorrowersListAdapter(borrowers, ::onBorrowerClick)
}
}
}
Upvotes: 1
Reputation: 8457
You could easily do so by adding a callback function to your Borrower
.
Given the type:
// UI stands for User Interface
data class BorrowerUI(val borrower: Borrower, val onClick: (Borrower) -> Unit)
Then change your RecyclerView.Adapter
to receive List<BorrowerUI>
instead of List<Borrower>
.
After that change a tiny bit your refreshData
implementation:
class BorrowersListVh(view: View): RecyclerView.ViewHolder(view) {
fun refreshData(borrowerUI: BorrowerUI) {
itemView.borrowersListName.text = borrower.name
itemView.borrowersLisDebt.text = borrower.debt.toString()
itemView.setOnClickListener {
Log.e("info", "Clicked")
borrowerUI.onClick(borrowerUI.borrower)
}
}
}
Finally in your Activity
when you create the RecyclerView.Adapter
class YourActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
thread {
var borrowers =
db.borrowers()
.getAll()
.map { BorrowerUI(it) { onBorrowerClick(it) } }
runOnUiThread {
val debtsSum = borrowers.sumByDouble { it.debt }
// update sum of debts
summary.text = debtsSum?.toBigDecimal().setScale(2, 2).toString()
// update item list
recycler.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = BorrowersListAdapter(borrowers)
}
}
}
}
fun onBorrowerClick(borrower: Borrower) {
// Create a toast?
}
}
Upvotes: 1
Reputation: 2859
1) This answer here works both on Kotlin and Java:
How to implement click listener callback
2) Kotlin Only (Lambdas):
In your Adapter:
Add a property named listener
that is a lambda you pass from your activity or fragment:
class MyAdapter(private val listener: (position: Int) -> Unit) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
...
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
listener.invoke(adapterPosition)
//or listener(adapterPosition)
}
}
}
In your Activity/Fragment onCreate()
after setting recyclerview's layout manager:
recyclerView.adapter = MyAdapter { position ->
//Do anything you like when some item gets click in your adapter you
//can use the position
}
Be careful with inner classes as they can lead to a Memory Leak in your app, for more safety you can switch to using Nested classes.
Upvotes: 1