Dominik
Dominik

Reputation: 1335

Recycler View Callback to activity

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

Answers (3)

Tenfour04
Tenfour04

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

Some random IT boy
Some random IT boy

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

Arrowsome
Arrowsome

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

Related Questions