How to move from one activity to another by clicking on recyclerview item and also pass some details of the clicked item in intent?
This is how my adapter class looks like
var onItemClick : ((Contest)->Unit)?=null
private lateinit var mListener: onItemClickListener
interface onItemClickListener{
fun onItemClick(position: Int)
}
fun setOnItemClickListener(listener: onItemClickListener){
mListener = listener
}
inner class MyViewHolder(itemView:View,listener: onItemClickListener):RecyclerView.ViewHolder(itemView ){
val name: TextView = itemView.findViewById(R.id.card_title)
val time: TextView = itemView.findViewById(R.id.card_time)
val venue: TextView = itemView.findViewById(R.id.card_venue)
val team: TextView = itemView.findViewById(R.id.card_team_type)
val prize: TextView = itemView.findViewById(R.id.card_prize)
val entry: TextView = itemView.findViewById(R.id.card_entry_division)
val join_btn: Button = itemView.findViewById(R.id.card_join_btn)
init {
itemView.setOnClickListener{
onItemClick?.invoke(contestlist[adapterPosition])
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.contest_item,parent,false)
return MyViewHolder(itemView,mListener )
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val contests = contestlist[position]
holder.name.text = contestlist[position].name
holder.entry.text = contestlist[position].entry
holder.prize.text = contestlist[position].prize
holder.team.text = contestlist[position].team
holder.time.text = contestlist[position].time
holder.venue.text = contestlist[position].venue
holder.join_btn.text = contestlist[position].total_entry
}
I think you've confused yourself by adding two different ways to set a listener.
Your function property var onItemClick
Your functional interface property private lateinit var mListener.
You need to delete one or the other. The first one is the one you should keep, because you are breaking a couple of conventions with your implementation of the second one, by misusing lateinit and by hiding a property behind setter function.
You're already correctly calling onItemClick?.invoke(contestlist[adapterPosition]) in the click listener of each view holder's root view. You just need to delete this stuff:
private lateinit var mListener: onItemClickListener
interface onItemClickListener{
fun onItemClick(position: Int)
}
fun setOnItemClickListener(listener: onItemClickListener){
mListener = listener
}
and delete the listener parameter in your ViewHolder constructor.
The class that creates the adapter (your Activity or Fragment) can set a lambda as the value of your adapter's onItemClick property and do the effect there:
adapter.onItemClick = { contest ->
// do something like open another fragment
}
Related
I'm really confused about how the Kotlin lambdas work, specifically with click listeners. I had something that was working to do a single ViewModel function in my MainFragment but now I want multiple buttons on my adapter that do different things. At first I thought I would just have to pass all the necessary information including IDs for the different buttons to the callback then do a switch statement in my main fragment that does the appropriate ViewModel functions. As soon as I changed my input parameters the adapter no longer accepted my OnClickListener argument.
First I'll show the old OnClickListener that was working.
ItemAdapter
class ItemAdapter(private val context: Context, private val onClickListener: OnClickListener) : ListAdapter<SongWithRatings, ItemAdapter.SongViewHolder>(SongsComparator())
{
lateinit var isVisible: BooleanArray
override fun onCurrentListChanged(
previousList: List<SongWithRatings>,
currentList: List<SongWithRatings>
) {
super.onCurrentListChanged(previousList, currentList)
if(previousList.size != currentList.size) {
isVisible = BooleanArray(itemCount)
isVisible.fill(element = false)
}
}
class SongViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
val textViewBpm: TextView = view.findViewById(R.id.bpm)
val lastPlayed: TextView = view.findViewById(R.id.lastPlayed)
//new rating code 11/27/2021
val ratingBar: SeekBar = view.findViewById(R.id.ratingBar)
val ratingLabel: TextView = view.findViewById(R.id.ratingLabel)
val button: Button = view.findViewById(R.id.submitRating)
//expandable view 3/27/2022
val titleView: LinearLayout = view.findViewById(R.id.titleView)
val expand: ConstraintLayout = view.findViewById(R.id.expand)
//fragment launch buttons
val moreButton: Button = view.findViewById(R.id.moreButton)
val rateButton: Button = view.findViewById(R.id.rateButton)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return SongViewHolder(adapterLayout)
}
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
val item = getItem(position)
//temporary code for initial rating
val initialRating = item.recentPerformanceRating()
var newRating = 0
holder.textView.text = item.song.songTitle
holder.textViewBpm.text = context.resources.getString(R.string.BPM,item.song.bpm)
holder.ratingLabel.setBackgroundColor(getStatusColor(item.recentPerformanceRating()))
//holder.imageView.setImageResource(item.imageResourceID)
holder.ratingLabel.text = context.resources.getString(R.string.Rating, initialRating )
holder.lastPlayed.text = item.lastPlayedString()
//rating bar functionality
holder.ratingBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, rating: Int, fromUser: Boolean) {
holder.ratingLabel.text = context.resources.getString(R.string.Rating, rating)
newRating = rating
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
//Performance Rating button functionality
holder.button.setOnClickListener{
onClickListener.onClick(item.song.songTitle, newRating)
}
holder.moreButton.setOnClickListener { }
class OnClickListener(val clickListener: (songTitle: String, newRating: Int) -> Unit) {
fun onClick(songTitle: String,newRating: Int) = clickListener(songTitle, newRating)
}
}
From MainFragment
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_view)
val adapter = ItemAdapter(requireContext(),
ItemAdapter.OnClickListener { songTitle, newRating ->
songViewModel.insertRating( Rating(System.currentTimeMillis(),songTitle,songViewModel.artistName, newRating )) }
)
Like I said, this all worked fine until I tried to use the OnClickListener with a SongsWithRatings parameter.
Am I even close here or do I have to redo my whole interface between the adapter, fragment and ViewModel?
You just have to make use of Interface for the purpose of providing listeners to the Fragment .
Step 1: Create an Interface Class.
interface ItemClickListener{
//You can include the parameters into the functions which you wish to be associated with the button. Suppose I want Title on Click of more Button, then I will pass it as a parameter
fun onButtonClick(val item : SongsWithRating)
fun onMoreButtonClicked(val title : String)
}
Step 2 : Create a listener variable in your adapter and call the functions in the onClick function of the respective buttons
class ItemAdapter(private val context: Context) : ListAdapter<SongWithRatings, ItemAdapter.SongViewHolder>(SongsComparator())
{
var listener : ItemClickListener ?= null
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
val item = getItem(position)
//Calling buttonClick and passing the function defined in the interface
along with the parameters
holder.button.setOnClickListener{
listener?.onButtonClick(item)
}
//Similarly for morebutton
holder.moreButton.SetOnClickListener{
listener?.onMoreButtonClicked(item.song.songTitle)
}
}
}
Step 3 : Now the final Step : Go to the fragment wherein the recyclerView is implemented
//Extent the Fragment with the Interface and override the methods
class Fragment : Fragment(), ItemClickListener{
//define Adapter and attach the listener
val adapter = ItemAdapter(requireContext()
adapter.listener = this
}
You are good to go
I am trying do I return the result of the position from my Recyclerview Adapter, but I can't call the " adapter.setOnItemClickListener(this)" from MainActivity.kt? I get the error "Unresolved reference: setOnItemClickListener"?
I've had to post a new thread as I can't get all the code to be shown - after taking quite a few hours failing on this I am losing the will to live :-(
Adapter.kt
class UsersAdapter(
private val users: ArrayList<User>
) : RecyclerView.Adapter<UsersAdapter.DataViewHolder>() {
class DataViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) {
fun bind(user: User) {
itemView.textViewUserName.text = user.name
Glide.with(itemView.imageViewAvatar.context)
.load(user.avatar)
.into(itemView.imageViewAvatar)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
DataViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_layout, parent, false)
)
override fun getItemCount(): Int = Int.MAX_VALUE
override fun onBindViewHolder(holder: DataViewHolder, position: Int){
val pos = position % users.size
holder.bind(users[pos])
Log.d(Constraints.TAG, "onBindViewHolder:" + pos)
}
lateinit var listener: OnItemClickListener
public interface OnItemClickListener {
fun getAdapterPosition(position : Int )
}
public fun setOnItemClickListener(listener: OnItemClickListener) {
this.listener= listener
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var adapter: ConcatAdapter
lateinit var userVerticalAdapter: UsersAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupDataInRecyclerView()
}
//override
public fun getAdapterPosition(position : Int ){
// required value is in the position variable
Log.d(Constraints.TAG, "This is where it is going!" )
}
private fun setupDataInRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(this,
LinearLayoutManager.HORIZONTAL, false)
userVerticalAdapter = UsersAdapter(DataSource.getUser())
val listOfAdapters = listOf(userVerticalAdapter)
adapter = ConcatAdapter(listOfAdapters)
//This errors with "Unresolved reference: setOnItemClickListener"
adapter.setOnItemClickListener(this)
recyclerView.adapter = adapter
recyclerView.scrollToPosition(Int.MAX_VALUE/2)
ItemSnapHelper().attachToRecyclerView(recyclerView)
}
public fun ShowWhatRecieved(isitthere: Int){
ShowMeIt.text = isitthere.toString()}
}
Revised and Updated MainActvity.kt
class MainActivity : AppCompatActivity(), UsersAdapter.OnItemClickListener {
lateinit var adapter: ConcatAdapter
lateinit var userVerticalAdapter: UsersAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupDataInRecyclerView()
}
override public fun getAdapterPosition(position : Int ){
Log.d(Constraints.TAG, "This is where it is going!" )
}
private fun setupDataInRecyclerView() {
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
userVerticalAdapter = UsersAdapter(DataSource.getUser())
val listOfAdapters = listOf(userVerticalAdapter)
adapter = ConcatAdapter(listOfAdapters)
adapter.setOnItemClickListener(this)
recyclerView.adapter = adapter
recyclerView.scrollToPosition(Int.MAX_VALUE/2)
ItemSnapHelper().attachToRecyclerView(recyclerView)
}
public fun ShowWhatRecieved(isitthere: Int){
ShowMeIt.text = isitthere.toString()
}
}
You are passing this as argument it will pass the context but in the parameter of function setOnItemClickListener in adapter you have defined listener of OnItemClickListener type. So you need to pass the argument of OnItemClickListener type.
You should implement the OnItemClickListener at your activity and you will be able to use this keyword. But you also can make an anonymous implementation of OnItemClickListener interface when you try to send.
But I would prefer the option 1. To implement the OnItemClickListener to your activity, implement all methods of that interface and than you can use this keyword when you need to pass OnItemClickListener anywhere in that class.
You have not implemented interface in your as below -
class MainActivity : AppCompatActivity(), RecycleViewCartAdapter.OnItemClickListener
Here is the problem mate.
Despite all your effort by creating an interface and implementing it inside your recycler-view, I suggest you use lambda instead of interface since you are writing Kotlin, and writing a lambda instead of an interface is a cleaner approach in Kotlin for ClickListener.
Create a lambda parameter both for your adapter and your viewholder. since you want to pass the position of a clicked item so your lambda must have an Int input and nothing as output so it would look like this: listener: (Int) -> Unit.
The rest of your code would be something like this, you get the idea I just edited important parts:
class UsersAdapter(private val users : ArrayList<CrashlyticsReport.Session.User>,
private val listener : (Int) -> Unit) :
RecyclerView.Adapter<UsersAdapter.DataViewHolder>() {
class DataViewHolder(itemView : View, private val listener : (Int) -> Unit) :
RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener { listener(adapterPosition) }
}
fun bind(user : CrashlyticsReport.Session.User) {
itemView.textViewUserName.text = user.name
Glide.with(itemView.imageViewAvatar.context).load(user.avatar).into(itemView.imageViewAvatar)
}
}
}
then in your activity you can write:
userVerticalAdapter = UsersAdapter(DataSource.getUser()) {
//clicklistener event here
}
or:
userVerticalAdapter = UsersAdapter(DataSource.getUser(),this::ShowWhatRecieved)
also since you're using ConcatAdapter it's better to use bindingAdapterPosition instead of adapterPosition.
I have a RecyclerView list, I want the color of the clicked item to be changed. But whenever I am clicking an item the RecyclerView items change randomly.
I have also taken a boolean in my data class to keep track of item selection.
data class
data class OrderAvailableDaysResponseItem(
val actual_date: String,
val day_name: String,
val date_name: String,
val day_num: Int,
// For item selection
var isDateSelected: Boolean)
Inside my fragment, checking if the clicked item matches the list item and updating the isDateSelected to true, then calling notifydatasetchanged.
private var availableDaysList: OrderAvailableDaysResponse = OrderAvailableDaysResponse()
orderAvailableDaysAdapter.setDateClickListener {
for(resItem in availableDaysList){
resItem.isDateSelected = resItem.date_name == it.date_name
}
orderAvailableDaysAdapter.notifyDataSetChanged()
}
Adapter clicklistener
var onDateClickListener: ((OrderAvailableDaysResponseItem) -> Unit)? = null
fun setDateClickListener(listener: (OrderAvailableDaysResponseItem) -> Unit){
onDateClickListener = listener
}
inner class AvailableDaysViewHolder(binding: ItemAvailableDaysBinding) : RecyclerView.ViewHolder(binding.root) {
fun setBindings(itemRes: OrderAvailableDaysResponseItem){
binding.resItem = itemRes
binding.executePendingBindings()
binding.clRoot.setOnClickListener {
onDateClickListener?.let {
it(itemRes)
}
}
}
}
Please refer to the attachment for a better understanding of the situation
As you can see on clicking on an item the items are changing randomly. Please help me. Am I missing something?
Edit 1:
Complete adapter code
class OrderAvailableDaysAdapter(var orderAvailableDaysResponseList: OrderAvailableDaysResponse) : RecyclerView.Adapter<OrderAvailableDaysAdapter.AvailableDaysViewHolder>() {
private lateinit var binding: ItemAvailableDaysBinding
var onDateClickListener: ((OrderAvailableDaysResponseItem) -> Unit)? = null
fun setDateClickListener(listener: (OrderAvailableDaysResponseItem) -> Unit){
onDateClickListener = listener
}
inner class AvailableDaysViewHolder(binding: ItemAvailableDaysBinding) : RecyclerView.ViewHolder(binding.root) {
fun setBindings(itemRes: OrderAvailableDaysResponseItem){
binding.resItem = itemRes
binding.executePendingBindings()
binding.clRoot.setOnClickListener {
onDateClickListener?.let {
it(itemRes)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AvailableDaysViewHolder {
binding = ItemAvailableDaysBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return AvailableDaysViewHolder(binding)
}
override fun onBindViewHolder(holder: AvailableDaysViewHolder, position: Int) {
holder.setBindings(orderAvailableDaysResponseList[position])
}
override fun getItemCount(): Int {
return orderAvailableDaysResponseList.size
}}
remove private lateinit var binding: ItemAvailableDaysBinding from adapter, don' keep it "globally", initialisation is made only once, in onCreateViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AvailableDaysViewHolder {
ItemAvailableDaysBinding binding = ItemAvailableDaysBinding.inflate(
LayoutInflater.from(parent.context),parent,false)
return AvailableDaysViewHolder(binding)
}
same naming of this "global" object and in AvailableDaysViewHolder inner class may confuse it and setBindings may be called on lastly initialised (global kept) object rather that this passed in constructor
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 am new on kotlin android. I have created the adapter for recyclerview. But I am not able to perform a click event for each recyclerview item. I need the explanation with the reference code.
Kindly help me to do this.
Thanks in advance.
Here is my code for your reference.
class CustomAdapter(val readerList: ReaderResponse, mainActivity:
MainActivity,val btnlistener: BtnClickListener) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
companion object {
var mClickListener: BtnClickListener? = null
}
override fun onCreateViewHolder(viewgroup: ViewGroup, index: Int): ViewHolder
{
val view=LayoutInflater.from(viewgroup?.context).inflate(R.layout.reader_list,viewgroup,false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return readerList.results.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
mClickListener = btnlistener
val item = readerList
val reader:ReaderData = readerList.results[position]
/*p0?.imageview?.text=reader.readerIcon*/
holder?.reader_status?.text=reader.readerStatus
holder?.ward_name?.text=reader.wardName
holder?.reader_id?.text=reader.readerID
holder?.reader_name?.text=reader.readerName
holder?.reader_location?.text=reader.readerLocation
if (reader.readerStatus.toLowerCase().equals("yes")){
holder.reader_name.setTextColor(Color.parseColor("#24a314"))
}else if (reader.readerStatus.toLowerCase().equals("no")){
holder.reader_name.setTextColor(Color.parseColor("#f4312d"))
holder.warning.setVisibility(View.VISIBLE)
}
}
class ViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val imageview = itemView.findViewById(R.id.imageview) as Button
val reader_name = itemView.findViewById(R.id.reader_name) as TextView
val reader_location = itemView.findViewById(R.id.floor_no) as TextView
val ward_name = itemView.findViewById(R.id.ward_name) as TextView
val reader_id = itemView.findViewById(R.id.reader_id) as TextView
val reader_status = itemView.findViewById(R.id.reader_status) as TextView
val warning=itemView.findViewById(R.id.warning) as Button
}
open interface BtnClickListener {
fun onBtnClick(position: Int)
}
}
You could use the following approach. This is taken from this blog by Antonio Leiva
Assuming your data class is ReaderData
class CustomAdapter(val readers: List, val listener: (ReaderData) -> Unit) {
/* Other methods */
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
/*...*/
holder.imageview.setOnClickListener {
listener(readers[position])
}
}
}
Now in your Activity or Fragment
recyclerview.adapter = CustomAdapter(readersList) { readerData ->
Log.i(TAG, "${readerData.readerID} clicked")
}
The idea is you pass a lambda which will be executed when your desired item is clicked.
You just need to implement BtnClickListener in the corresponding Activity in which this adapter is initialized. Once you have implemented the BtnClickListener it would override the function onBtnClick in the activity.
The only thing you need to do in the adapter is to initialize the onClickListener on the element you need and in that method just call imageview.setOnClickListener { mClickListener?.onBtnClick(position) }. It would send the position back in activity and you can perform your specific task there. For example I have implemented the ClickListener in one Activity and printed the log there it works fine. Below is the demo code for it.
class Main2Activity : AppCompatActivity(), CustomAdapter.BtnClickListener {
override fun onBtnClick(position: Int) {
Log.d("Position", position.toString())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL, false)
val readerResponseList = ArrayList<YourModelClassName>()
val adapter = CustomAdapter(readerResponseList,this,this)
recyclerView.adapter = adapter
}
Hope it Helps.