I have an Adapter:
class TripsAdapter(
onClickButtonAction: (TripId : Int) -> Unit,
): ListAdapter<Trip, TripssViewHolder>(TripsDiffCallBack()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TripssViewHolder {
return TripsViewHolder(
ItemsTripsBinding.inflate(
LayoutInflater.from(parent.context), parent, false), parent.context)
}
override fun onBindViewHolder(holder: TripsViewHolder, position: Int) {
holder.bind(getItem(position), onClickButtonAction ) <-- here I don't know how pass this function to ViewHolder
}
}
And here is bind function in ViewHolder:
fun bind(trip: Trip, onClickButtonAction : (Trip: Int) -> Unit) <- here is wrong code I think{
// And here I want to reaact on button click and pass ID to fragment like this:
binding.button.setOnClickListener{
onClickButtonAction.invoke()
}
}
And I want to receive it in Fragment like this:
TripsAdapter = TripsAdapter(onClickButtonAction = { id ->
do magic :P
})
Is it posible to pass this ID like funciton? I dont want to use interface. I want to pass Id on click from ViewHolder to Fragment.
You can pass a method as lambda as a callback . And since its a callback it should be Global so you do not have to pass it to ViewHolder further. Also u need to declare the lambda as val so that u can access it in the class.
I have created an example below .
class TripAdapter(val onClickButtonAction: (trip : Trip, view:View?) -> Unit) : ListAdapter<Trip, TripAdapter.TripsViewHolder>(TripsDiffCallBack) {
inner class TripsViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(trip: Trip) {
binding.button.setOnClickListener {
onClickButtonAction(trip, it)
}
}
}
}
this is how your lambda will look i have changed the parameters just in case . passing whole object of Trip and also passing the clicked View this can be helpful for handling clicks on multiple views.
Now wherever u are using this Adapter you create its as below :-
val adapter = TripAdapter(::onTripClickListener)
private fun onTripClickListener(trip:Trip, view: View?){
}
Related
I have a recyclerView in my kotlin app that shows a list of items, when i click on one my app navigates to other fragment that shows the details of that recyclerview item, my problem is when I filter the results, it uses the adapterPosition that in this case, its different from the position of the data in the json.
When I filter the data with searchView, I submit to the adapter the new list with the filters applied.
Fragment where the recyclerview is:
(Here i would like to send as a string one of the fields shown in the recyclerview item i click)
private var museumsListAdapter=MuseumsListAdapter{ it ->
val bundle = Bundle().apply {
putInt(INDICE,it)
}
findNavController().navigate(R.id.action_ListMuseumsFragment_to_detailsMuseumFragment, bundle)
}
Adapter of the recyclerView:
class MuseumsListAdapter(private val onMuseumSelected: (Int) -> Unit):
ListAdapter<MuseumsFieldsItem, MuseumsListViewHolder>(MuseumsFieldsItem.DIFF_CALLBACK) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MuseumsListViewHolder {
val itemView=MuseumListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MuseumsListViewHolder(itemView.root){
onMuseumSelected(it)
}
}
override fun onBindViewHolder(holder: MuseumsListViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
ViewHolder of the recyclerview:
lass MuseumsListViewHolder(itemView: View, private val onItemClicked: (Int) ->Unit) : RecyclerView.ViewHolder(itemView) {
val itemBinding=MuseumListItemBinding.bind(itemView)
init {
itemView.setOnClickListener{
onItemClicked(adapterPosition)
}
}
And in the other fragment (details one) I get the value "INDICE" of the bundle.
class MuseumsListAdapter(private val onMuseumSelected: (Int) -> Unit):
this is a callback which takes in an int, so just don't use int :)
class MuseumsListAdapter(private val onMuseumSelected: (Foo) -> Unit):
where Foo represents whatever you model is, make use of Museum in your case
inside your:
itemView.setOnClickListener{
onMuseumSelected(adapterPosition)
}
you now need an instance of your Museum class.
by using private val onMuseumSelected: (Foo) -> Unit you'll get back a complete model to your fragment/activity, so you can use whatever field you need
I'm new in Android dev.
In my code i don't using onClick method, but i using setOnClickListener and Callback. The main problem that is in this way i don't know how to get the position of the item in RecyclerView.
Here is my Adapter:
class TestAdapter(val test : ArrayList<Test>, private val testAdapterCallback: (Test, Int)->Unit) : RecyclerView.Adapter<TestAdapter.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.test_view_item, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return test.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val num : Test = test[position]
holder.textView.text = num.id.toString()
holder.cardView.setTag(position)
holder.cardView.setOnClickListener(){
testAdapterCallback(num)
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val cardView = itemView.findViewById<CardView>(R.id.testCardView)
val textView = itemView.findViewById<TextView>(R.id.testTextView)
}
}
I have added second parametr into callback but i don't know how i must change my adapter's inicializations in this peace of code:
val adapter = TestAdapter(list) { item ->
testAdapterItemClick(item)
}
In activity i'm using this method :
private fun testAdapterItemClick(item: Test) {}
Please, help me to check the position of the choosen element. I need it later.
Thanks in advance)
P.S. Sorry for my English
Add the position as parameter in the callback.
So, instead of: private val testAdapterCallback: (Test)->Unit
Use: private val testAdapterCallback: (Test, Int)->Unit.
This way you can pass the position in the callback.
holder.cardView.setOnClickListener(){
testAdapterCallback(num, position)
}
In your activity:
val adapter = TestAdapter(list) { item, position ->
testAdapterItemClick(item)
}
Create interface for your OnClickListener
interface OnClickListener{
fun clickItem(test: Test, index: Int)
}
Pass listener to your adapter like below.
class TestAdapter(
var test : ArrayList<Test>?,
val clickListener: OnClickListener,
var mActivity: Activity
) :
RecyclerView.Adapter<TestAdapter.MyViewHolder>() {
}
Now in your onBindViewHolder add click listener.
var mtest= test !!.get(i)
holder.cardView.setOnClickListener {
clickListener.clickItem(mtest, i)
}
Implement Listener to your activity and initialize it.
this.mOnClickListener = this
Pass listener to your adapter where you passing the arraylist.
mTestAdapter = TestAdapter(arrayList, mOnClickListener ,mActivity)
You'll get the position in your activity override method.
override fun editItem(mTest: Test, index: Int) {
if (mTest!= null) {
}
}
I want to set an onclicklistener in the onBindViewHolder in order to navigate to a different fragment and send along some data to that fragment.
For the life of me, I can't seem to find a way to make it work. Any and all help is greatly appreciated!
The adapter class:
class ListAdapter(private val list: List<Workout>): RecyclerView.Adapter<WorkoutViewHolder>() {
override fun getItemCount(): Int{
return list.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WorkoutViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return WorkoutViewHolder(layoutInflater, parent)
}
override fun onBindViewHolder(holder: WorkoutViewHolder, position: Int) {
val workout: Workout = list[position]
holder.itemView.setOnClickListener{
Toast.makeText(holder.itemView.context, "TEST", Toast.LENGTH_LONG).show()
val id = workout.workoutId
val bundle = Bundle()
bundle.putInt("workoutId", id)
Navigation.createNavigateOnClickListener(R.id.workoutDetailsFragment)
}
holder.bind(workout)
}
}
I can get the toast to pop up, so the onclicklistener seems to be working. However, the navigation part does not work.
If I just set a button inside the fragment that is hosting the recyclerview and add button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.workoutDetailsFragment)) it can navigate just fine. So the problem seems to be calling the navigate function from inside the onclicklistener inside the onbindviewholder
Navigation.createNavigateOnClickListener() creates an OnClickListener. Creating an OnClickListener just to never set it on anything doesn't do anything.
Instead, you'll want to just trigger your navigate() call directly, doing the same one line of code that createNavigateOnClickListener does internally:
override fun onBindViewHolder(holder: WorkoutViewHolder, position: Int) {
val workout: Workout = list[position]
holder.itemView.setOnClickListener{
Toast.makeText(holder.itemView.context, "TEST", Toast.LENGTH_LONG).show()
val id = workout.workoutId
val bundle = Bundle()
bundle.putInt("workoutId", id)
// Using the Kotlin extension in the -ktx artifacts
// Alternatively, use Navigation.findNavController(holder.itemView)
holder.itemView.findNavController().navigate(
R.id.workoutDetailsFragment, bundle)
}
holder.bind(workout)
}
You need to assign your created listener rather than using it inside of a lambda. When you use a lambda with setOnClickListener(), the lambda literally is your listener. So in your example, you're creating a listener, but it's never assigned anywhere.
So to instead assign the created listener from Navigation.createNavigateOnClickListener(), your code should look like holder.itemView.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.workoutDetailsFragment))
I'm trying to use setOnClickListener and setOnLongClickListener in adapter to pass the click to the activity. I've searched a lot of and I only found some examples of how to do one clicklistener but not handle both at the same time.
How can I handle both listeners in the activity?
ADAPTER
class BrowserAdapter(private val voucherList: List<String>?, private val listener: (String) -> Unit) : RecyclerView.Adapter<BrowserAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(inflate(parent.context, R.layout.item_web, parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(voucherList!![position], listener)
}
override fun getItemCount(): Int {
return voucherList!!.size
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(urlData: String, listener: (String) -> Unit) = with(itemView) {
tx_url.text = urlData
itemView.setOnClickListener{
listener(urlData)
}
itemView.setOnLongClickListener{
listener(urlData)
true
}
}
}
}
ACTIVITY
rv_web_items.adapter = BrowserAdapter(Preferences.getFavouritesWebsites()) {
presenter.onItemClick(it)
}
The lambda function of the activity should look like :-
val listener : (String, Boolean) -> Unit = { urlData, isLongClick -> presenter.onItemClick(urlData) }
Then pass the lambda ( listener ) to the adapter
rv_web_items.adapter = BrowserAdapter(Preferences.getFavouritesWebsites(), listener)
I will suggest you to add listeners at onBindViewHolder like this:
holder.itemView.setOnClickListener {
}
and also long click:
holder.itemView.setOnLongClickListener { true }
and what about sending data to activity. First step - create interface:
interface Click {
fun sendData(..., position: Int, ...) // it is only example
}
the the second step use it at your adapter:
open class Adapter(..., ..., private val click: Click)
then handle click and send data:
holder.itemView.setOnClickListener {
click.sendData(your_data)
}
then in activity you have to declare this interface:
val adapter = Adapter(..., ..., this#YourActivity)
function for getting data in your activity:
override fun sendData(your_data) {
}
and also don't forget to implement this interface:
class JobsList : ..., ..., Click
I hope it will help you. Good Luck :)
Now that I'm using kotlin as my main programing language I'm trying to simplify Android's Recyclerview implementation logic and I'm stuck at following point:
Let's say I have defined the following ViewHolder class, where a click position event is propagated via a method reference in the constructor.
class CustomViewHolder(itemView: View, val onClick: (Int) -> Unit)) : RecyclerView.ViewHolder(itemView){
itemView.setOnClickListener {
onClick.invoke(adapterPosition)
}
}
How could I build an instance of a generic class that has a method reference in the constructor?
My recyclerview implementation has a generic class in the constructor and also an entity reference to be able to create an instance of my CustomViewHolder class by reflection at onCreateViewHolder:
class CustomAdapter<HolderClassGeneric>(val entityClass: Class<HolderClassGeneric>) : RecyclerView.Adapter<HolderClassGeneric>() {
/*
Usual recyclerview stuff
*/
//Method that I want to be called when a list item is clicked
fun onListItemClicked(position: Int) {
//do stuff
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HolderClassGeneric {
val view = LayoutInflater.from(parent.context).inflate(listItemLayoutResourceId, parent, false)
val constructor: Constructor<HolderClassGeneric>
val instance: HolderClassGeneric
//I'M STUCK HERE
constructor = entityClass.getConstructor(View::class.java, ?¿?¿?¿?¿)
instance = constructor.newInstance(view, this::onListItemClicked)
return instance
}
If what I want to achieve is not possible, would it be possible to pass the method reference using a setter by reflection?
A Kotlin lambda is usually translated as a FunctionX object, being X the number of parameters it has, so if you're looking for the representation of a lambda function like (Int) -> Unit it would be Function1<Int, Unit> so you could simply do:
entityClass.getConstructor(View::class.java, Function1::class.java)
You can use a parameter function like a factory.
class CustomAdapter<HolderClassGeneric>(val entityFactory: (View, (Int) -> Unit) -> HolderClassGeneric) : RecyclerView.Adapter<HolderClassGeneric>() {
/*
Usual recyclerview stuff
*/
//Method that I want to be called when a list item is clicked
fun onListItemClicked(position: Int) {
//do stuff
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HolderClassGeneric {
val view = LayoutInflater.from(parent.context).inflate(listItemLayoutResourceId, parent, false)
val instance = entityFactory(view, this::onListItemClicked)
return instance
}