I'm new in Kotlin, I'm using databinding library to set items in recyclerview which I'm getting from viewmodel and calling clicklistener inside activity class. I have defined an interface in my adapter class and implemented in activity but nothing happens when I click on the recyclerview item and also not getting any error. I'm unable to figure out the mistake
My adapter looks like this
class NewsAdapter (private var listener: OnNewsClickListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mCategoryList = ArrayList<Newslist>()
fun setAppList(categoryModel: ArrayList<Newslist>) {
mCategoryList.addAll(categoryModel)
//notifyItemRangeInserted(0, categoryModel.size)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
Log.d("LIST_SIZE","" + mCategoryList.size)
return mCategoryList.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val appInfo = mCategoryList[position]
(holder as NewsAdapter.RecyclerHolderCatIcon).bind(appInfo, listener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val applicationBinding = ItemNewsBinding.inflate(layoutInflater, parent, false)
return RecyclerHolderCatIcon(applicationBinding)
}
interface OnNewsClickListener {
fun onNewsClick(position: Int)
}
inner class RecyclerHolderCatIcon(private var applicationBinding:ItemNewsBinding) : RecyclerView.ViewHolder(applicationBinding.root) {
fun bind(appInfo:Newslist, listener: OnNewsClickListener?) {
applicationBinding.newsItem = appInfo
}
}
}
Because you have not set listener for for item layout.
You don't need to define OnNewsClickListener interface.
inner class RecyclerHolderCatIcon(private var applicationBinding:ItemNewsBinding) : RecyclerView.ViewHolder(applicationBinding.root) {
fun bind(appInfo:Newslist) {
applicationBinding.root.setOnClickListener{ /*handle click here*/ }
}
}
You need to set the click listener in bind like this.
Here is a sample code, you will need to modify it
fun bind(appInfo:Newslist, listener: OnNewsClickListener?) {
applicationBinding.newsItem = appInfo
itemView.setOnClickListener( view -> {
if (listener != null) {
listener.onNewsClick();
}
}
}
Related
Please tell me how to transfer the ID (position) of the view element on recyclerview to another class?
class CardAdapter : RecyclerView.Adapter<CardAdapter.CardViewHolder>(), View.OnClickListener {
private var cardList = ArrayList<Card>()
private lateinit var card: Card
class CardViewHolder(
val binding: FragmentCardBinding
) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = FragmentCardBinding.inflate(inflater, parent, false)
return CardViewHolder(binding)
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
card = cardList[position]
..
}
override fun getItemCount(): Int = cardList.size
override fun onClick(v: View) {
when (v.id) {
R.id.root_card_template -> {
val intent = Intent(v.context, ProductActivity::class.java)
// need put id into ProductActivty
intent.putExtra("item", card.id)
v.context.startActivity(intent)
}
}
}
At the moment, it only passes the ID of the last generated element. For some reason there is almost no information on the Internet on this score
Just create an interface and implement that in the calling activity. While creating an instance of your adapter inside the activity, pass that interface along and on the click event of the view in the adapter class, call the interface's method with the data that you want to pass back to the activity.
interface OnItemClickListener{
fun onClick(pos: Int)
}
class YourActivity: AppCompatActivity(), OnItemClickListener {
override fun onStart() {
super.onStart()
//Create an instance of your adapter and pass the interface.
// Here #this context is being passed as Activity is implementing the interface.
val cardListAdapter = CardListAdapter(this)
}
override fun onClick(pos: Int) {
//Add your logic
}
}
// Then in your adapter class
class YourAdapter(private val itemClickListener: OnItemClickListener) :
RecyclerView.Adapter<CardListAdapter.CardViewHolder>() {
//Your code
override fun onBindViewHolder(holder: CardListAdapter.CardViewHolder, position: Int) {
yourView.setOnClickListener {
itemClickListener.onClick(position)
}
}
}
first you need to creat an interface
interface OnItemListener {
fun onItemSelect(position: Int)
}
then in your class that calls the recyclerview adapter, pass it to your recyclerView Adapter like this
var cardAdapter = CardAdapter(object :
OnItemListener {
override fun onItemSelect(position: Int) {
// you can handle your data here
// your position that you passed comes here
}
})
var layoutManager = GridLayoutManager(context, 2)
yourRecyclerViewId.adapter = cardAdapter
yourRecyclerViewId.layoutManager = layoutManager
finally in your adapter do like this
class CardAdapter(
private val onItemListener: OnItemListener
)
: RecyclerView.Adapter<CardAdapter.CardViewHolder>(), View.OnClickListener {
private var cardList = ArrayList<Card>()
private lateinit var card: Card
and in your on click event in the adapter call it as below:
onItemListener.onItemSelect(yourPosition)
i wanna ask something about my code.
so, I wanna retrieve the data that i have selected in the checkbox recyclerview, and then put it into variabel arraylist. and then when i click button next, i can use variabel arraylist in main code and then i can move to next page. can you help me to add that code on my coding?
anyway i use kotlin, so i need code kotlin
here my main coding
private fun getData() {
mDatabaseReference.addValueEventListener(object : ValueEventListener {
override fun onCancelled(databaseError: DatabaseError) {
Toast.makeText(this#ChooseCategoryActivity, ""+databaseError.message, Toast.LENGTH_SHORT).show()
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
dataCategory.clear()
for(getdataSnapshot in dataSnapshot.getChildren()) {
Log.i("dataKey", getdataSnapshot.key.toString())
val kategori = getdataSnapshot.getValue(Kategori::class.java)
dataCategory.add(kategori!!)
}
rv_user_kategori.adapter = UserCategoryAdapter(dataCategory) {
}
}
})
here my adapter recyclerview
class UserCategoryAdapter(private var data: List<Kategori>, private var listener: (Kategori) -> Unit) : RecyclerView.Adapter<UserCategoryAdapter.ViewHolder>() {
lateinit var ContextAdapter : Context
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
ContextAdapter = parent.context
val inflatedView = layoutInflater.inflate(R.layout.layout_row_kategori, parent, false)
return ViewHolder(inflatedView)
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: UserCategoryAdapter.ViewHolder, position: Int) {
holder.bindItem(data[position], listener,ContextAdapter, position)
}
class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
private val namaKategori:TextView = v.findViewById(R.id.txt_row_kategori)
var checkbox:CheckBox = v.findViewById(R.id.txt_row_kategori)
fun bindItem(data: Kategori, listener: (Kategori) -> Unit, context : Context, position : Int) {
namaKategori.text = data.nama
itemView.setOnClickListener {
listener(data)
}
}
}
}
please help me to find that solution.
Your listener currently return only the Kategori. Make it return also the position of the item on the list and/r the status of the checkbox. You can get the state of the checkbox with Checkbox.isChecked.
To use a listener you need to add invoke, so the flow should be :
Your adapter declaration
class UserCategoryAdapter(private var data: List<Kategori>, private var listener: (Kategori) -> Unit) : RecyclerView.Adapter<UserCategoryAdapter.ViewHolder>()
Then in your bind you send the listener via constructor as well
holder.bindItem(data[position], listener,ContextAdapter, position)
Once you are in the ViewHolder and you want to detect the state change listener you need to call it like
itemView.setOnClickListener { listener.invoke(data) }
What's the objective
Im currently working on an app which has a RecyclerView for the Settings menu. This menu serves to load other fragments. So i needed to implement an OnItemClick function: for this, i followed this video.
What's the probelm
Following the given tutorial, Android Studio flags val adapter = adapterSettings(settingsList), saying No value passed for parameter 'listener'. I suppose that im missing something, since without the code written in the tutorial, the RecyclerView works.
So, am i missing something? Are there any ways to fix this in an easy and clean way?
Code:
activitySettings.kt
class ndActSettings : AppCompatActivity(), adapterSettings.OnItemClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ndactivity_settings)
topToolbarBack.setNavigationOnClickListener {
finish()
}
var settingsList = listOf(
dataItemsSettings(getString(R.string.look), getString(R.string.lookdescription), R.drawable.ic_colored_color_lens),
dataItemsSettings(getString(R.string.reproduction), getString(R.string.reproductiondescription), R.drawable.ic_colored_view_carousel),
dataItemsSettings(getString(R.string.images), getString(R.string.imagesdscription), R.drawable.ic_colored_image),
dataItemsSettings(getString(R.string.audio), getString(R.string.audiodescription), R.drawable.ic_colored_volume_up),
dataItemsSettings(getString(R.string.about), getString(R.string.aboutdescription), R.drawable.ic_colored_info)
)
val adapter = adapterSettings(settingsList) //ERROR HERE!
rvSettings.adapter = adapter
rvSettings.layoutManager = LinearLayoutManager(this)
}
override fun OnItemClick(position: Int) {
//TODO
}
}
adapterSettings.kt
class adapterSettings(
var settingsList: List<dataItemsSettings>,
var listener: OnItemClickListener
) : RecyclerView.Adapter<adapterSettings.SettingsViewHolder>() {
inner class SettingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
val position : Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.OnItemClick(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
return SettingsViewHolder(view)
}
override fun getItemCount(): Int {
return settingsList.size
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.itemView.apply {
rvTitle.text = settingsList[position].stringTitle
rvDescription.text = settingsList[position].stringDescription
rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
interface OnItemClickListener {
fun OnItemClick(position: Int)
}
}
The constructor of class adapterSettings is expecting two parameters
class adapterSettings(
var settingsList: List<dataItemsSettings>,
var listener: OnItemClickListener
)
However, you are instantiating the object with one parameter only:
val adapter = adapterSettings(settingsList)
So, you have to add a second parameter.. An object that implements OnItemClickListener. Since you activity already implements that interface, you can send the activity as second parameter:
val adapter = adapterSettings(settingsList, this)
I am trying to build a Slack-like chat app following a tutorial in a course I am taking online.
In the tutorial the instructor is using a ListView and the OnItemClickListener method, but I am trying to do it with recycler view, and I am stuck with the onClickListener in the adapter.
I have tried to find answers in other questions but couldn't find one that solves my problem. The closest ones were this and this
My two problems are:
1. The app's main activity has on the top of the screen a title that states what channel is currently active. I have created a singleton that holds the "current channel" and the title's text is being pulled from that singleton.
I am having a hard time changing the value of that singleton on click.
The main activity also has all the channels in a list view in a drawer.
I am trying to close the drawer when a channel is clicked but that isn't happening either.
This is my current adapter:
class ChannelsAdapter(val context: Context, val channels: ArrayList<Channel>) :
RecyclerView.Adapter<ChannelsAdapter.Holder>() {
inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val singleChannel = itemView.findViewById<TextView>(R.id.single_channel)
val mainLayout = LayoutInflater.from(context).inflate(R.layout.activity_main, null)
fun bindText(textVar: String, context: Context) {
singleChannel.text = textVar
}
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bindText(channels[position].toString(), context)
holder.itemView.setOnClickListener {
ChannelName.activeChannel = channels[position]
holder.mainLayout.drawer_layout.closeDrawer(GravityCompat.START)
}
}
override fun getItemCount(): Int {
return channels.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelsAdapter.Holder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.channel_list_layout, parent, false)
return Holder(view)
}
}
This is the singleton
object ChannelName {
var activeChannel : Channel? = null
}
You can rewrite the setter for activeChanell variable and call a listener that has been added before to notify your Activity:
object ChannelName {
private val listeners = ArrayList<(Channel?) -> Unit>()
fun addChannelNameChangedListener(listener: (Channel?) -> Unit) {
listeners.add(listener)
}
fun removeChannelNameChangedListener(listener: (Channel?) -> Unit) {
listeners.remove(listener)
}
var activeChannel: Channel? = null
set(value) {
field = value
listeners.forEach { it.invoke(value) }
}
}
And inside the Activity add a listener like this:
ChannelName.addChannelNameChangedListener {
// Do your operation
}
The alternative solution is to use Observable utils like LiveData, so you shouldn't worry about the Android life cycle any more:
object ChannelName {
val activeChannel: MutableLiveData<ChannelName> = MutableLiveData()
}
To change the value inside your adapter simply call:
ChannelName.activeChannel.value = channels[position]
And inside your activity Observe to the variable by calling:
ChannelName.activeChannel.observe(this, Observer {
// Do your operation
})
class ChannelsAdapter(val context: Context, val channels: ArrayList<Channel>) :
RecyclerView.Adapter<ChannelsAdapter.Holder>() {
private var itemClickListener: OnItemClickListener? = null
fun setItemClickListener(itemClickListener: OnItemClickListener) {
this.itemClickListener = itemClickListener
}
interface OnItemClickListener {
fun onItemClick(position: Int)
}
inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
val singleChannel = itemView.findViewById<TextView>(R.id.single_channel)
val mainLayout = LayoutInflater.from(context).inflate(R.layout.activity_main, null)
fun bindText(textVar: String, context: Context) {
singleChannel.text = textVar
}
override fun onClick(v: View?) {
val position = adapterPosition
itemClickListener?.let {
if (position != RecyclerView.NO_POSITION) {
it.onItemClick(position)
}
}
}
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bindText(channels[position].toString(), context)
}
override fun getItemCount(): Int {
return channels.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChannelsAdapter.Holder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.channel_list_layout, parent, false)
return Holder(view)
}
}
This way you can setItemClickListener to the adapter in your activity and get callback from your recyclerView.
You should not set listener in onBind() method since it will be called more than your items' count.
I have a recyclerView adapter named ArticleAdapter
import kotlinx.android.synthetic.main.articlerecycler_item.view.*
class ArticleAdapter(private val controller: IController) : RecyclerView.Adapter<ArticleViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.articlerecycler_item, parent, false)
return ArticleViewHolder(view)
}
// How implement a button click for `btnSave`
/** Called when the user taps the Save button */
btnSave.setOnClickListener(){
(R.layout.articlerecycler_item)
AlertDialog.Builder(this)
.setMessage("Article Saved.")
.create()
.show()
// Do something in response to button click
}
override fun getItemCount(): Int {
return controller.articles.size
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article: Article = controller.articles[position]
if(article.title.length > 100) {
holder.itemView.titleTv.text = "${article.title.substring(0, 99)}..."
} else {
holder.itemView.titleTv.text = article.title
}
holder.itemView.authorTv.text = article.author
}
}
class ArticleViewHolder(view: View?) : RecyclerView.ViewHolder(view)
Then in my recyclerView layout I have a button
<Button
android:id="#+id/btnSave"
android:layout_width="70dp"
android:layout_height="40dp"
android:onClick="btnSave"
android:text="#string/save"
android:textAlignment="center" />
Do I handle the event inside onBindViewHolder? I'm not sure how to implement the listener within my recyclerView adapter
A good solution to handle the items click of an adapter is to pass them to the activity or fragment which has created them.
With kotlin you can use function definition instead of an interface for this simple work, so inside your adapter class add a var of a function type:
private var listener: ((item: DataClass) -> Unit)? = null
fun setOnItemClickListener(listener: (item: DataClass) -> Unit) {
this.listener = listener
}
And inside the ViewHolder class set the listener, something like below:
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.btn_save.setOnClickListener { listener?.invoke(data[adapterPosition]) }
}
// ...
}
Finally inside your activity or fragment you can easily access the items by adding a new listener:
adapter.setOnItemClickListener { it -> TODO() }
Notice that setting listeners inside ViewHolder constructor(init) is more efficient as it is not necessary to set a listener for every data binding.
I suppose you should add an extension lister. Here's https://dzone.com/articles/click-listener-for-recyclerview-adapter.
Also, you must add the custom listener for the save button.
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article: Article = controller.articles[position]
if(article.title.length > 100) {
holder.itemView.titleTv.text = "${article.title.substring(0, 99)}..."
} else {
holder.itemView.titleTv.text = article.title
}
holder.itemView.authorTv.text = article.author
*holder.btnSave.setOnClickListener(){_ ->
**yourCustomListener**.OnClick(data/position) }*
}
You have to handle it in onBindViewHolder() but logic which is responsible for creating and showing AlertDialog shouldn't be located in RecyclerView. You need to create an interface, which will be responsible for passing onClick() event information from your adapter to activity/fragment, and there you should show AlertDialog.
class ArticleAdapter(private val controller: IController) : RecyclerView.Adapter<ArticleViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.articlerecycler_item, parent, false)
return ArticleViewHolder(view)
}
override fun getItemCount(): Int {
return controller.articles.size
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article: Article = controller.articles[position]
if(article.title.length > 100) {
holder.itemView.titleTv.text = "${article.title.substring(0, 99)}..."
} else {
holder.itemView.titleTv.text = article.title
}
btnSave.setOnClickListener {
listener?.onSaveButtonClick()
}
holder.itemView.authorTv.text = article.author
}
private var listener: OnClickListener? = null
fun setListener(listener: OnClickListener) {
this.listener = listener
}
}
interface OnClickListener {
fun onSaveButtonClick()
}
class ArticleViewHolder(view: View?) : RecyclerView.ViewHolder(view)
Now pass the instance of OnClickListener from your activity to your adapter by setListener() method.