Iam trying without succes to add detailview (onclick listener) after click on recyclerview item.
Iam using viewbinding and thats new to me.
My code for the adapter:
class MyRecyclerAdapter(listitems: MyFragment) : RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder>() {
var items: ArrayList<ListItems> = ArrayList()
// class MyViewHolder(val binding: LayoutMyListBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutMyListBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
class MyViewHolder(var LayoutMyListBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(appInfo: ListItem) {
binding.root.setOnClickListener {
println("clicked for detail")
}
}
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.binding.listItem.text = items[position].titel
}
fun submitList(ItemsList: ArrayList<ListItem>) {
items = ListItems as ArrayList<ListItem>
}
}
How can i implement the click listener with viewbinding in the adapter ? i know i can do holder.binding.setonclicklistener. But this is a bad option.
I I solved it so set an click listener on the ViewHolder
The bind() never get called, so call it in onBindViewHolder
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.binding.vacatureTitel.text = items[position].titel
holder.binding.vacatureWerkgever.text = items[position].werkgever.bedrijfsnaam + " gevestigd in " + items[position].werkgever.werkgever_plaatsnaam
holder.bind(items[position]) // <<< Change here
}
I solved it to put an clicklistener on the Viewholder.
Related
I need to add empty constructor to my Adapter, but i have already primary constructor.
My code:
class RvStatesAdapter(private var stateList: List<State>): RecyclerView.Adapter<RvStatesAdapter.MyViewHolder>() {
inner class MyViewHolder(val binding: RvStateListBinding): RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(RvStateListBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return stateList.size
}
I have tried to use costructor(): this() but in this case i dont understand what i need to put in this() brackets
You can make Adapter constructor without params by instead of passing list in primary constructor use function to submit list in adapter.
class RvStatesAdapter(): RecyclerView.Adapter<RvStatesAdapter.MyViewHolder>() {
private val stateList: ArrayList<State> = ArrayList<State>()
/**
* submit list to recycler view adapter for populating items
*/
fun submitList(list: List<State>) {
stateList.addAll(list)
}
inner class MyViewHolder(val binding: RvStateListBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
RvStateListBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return stateList.size
}
}
Submit list using adapter instance
val list = emptyList<State>()
val rvStatesAdapter = RvStatesAdapter()
// init recyclerview properties
rvStatesAdapter.submitList(list)
In my app, there is an Activity which has a RecyclerView inside, which loads the list of options needed for that screen.
In the code below, i tried to implement a binder, which is needed because of the recent Android changes.
However, when i open the activity starts, the application crashes, throwing this error, linking the line with binding = ItemSettingsBinding.bind(binding.root):
kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized
What am i doing wrong? What's the correct way to implement a binder inside an adapter?
AdapterSettings.kt
class AdapterSettings(
var settingsList: List<DataItemSettings>,
var listener: OnItemClickListener
) : RecyclerView.Adapter<AdapterSettings.SettingsViewHolder>() {
private lateinit var binding: ItemSettingsBinding
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) {
binding = ItemSettingsBinding.bind(binding.root)
holder.itemView.apply {
binding.rvTitle.text = settingsList[position].stringTitle
binding.rvDescription.text = settingsList[position].stringDescription
binding.rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
interface OnItemClickListener {
fun OnItemClick(position: Int)
}
}
I believe you're missing your inflate in onCreateViewHolder:
// Pseudo-Code
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val binding = ItemSettingsBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return SettingsViewHolder(binding)
}
Then you can make use of it.
Create the binding in onCreateViewHolder and pass the binding into the ViewHolder instead of the inflated View. Thus you create a binding for each created view and only need to do the apply stuff in the onBindViewHolder
Example:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
val binding = ItemSettingsBinding.bind(view)
return SettingsViewHolder(binding)
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.binding.apply {
rvTitle.text = settingsList[position].stringTitle
rvDescription.text = settingsList[position].stringDescription
rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
Adapt your ViewHolder accordingly
There is indeed another way to ViewBind in an adapter.
First, we need to setup the ViewHolder in a different way:
inner class SettingsViewHolder(private val binding: ItemSettingsBinding):
RecyclerView.ViewHolder(binding.root), View.OnClickListener {
With this, we created a binding value inside the brackets, so we are able to call the items of the actual view or layout trough binding.root
Inside the viewholder, we need to create a function used to bind our items. We can either bind like this:
fun bind(item: Item) {
binding.item = item
binding.executePendingBindings()
}
Or like this:
fun bind(item: DataItemSettings) {
binding.rvTitle.text = settingsList[position].stringTitle
binding.rvDescription.text = settingsList[position].stringDescription
binding.rvIcon.setImageResource(settingsList[position].itemIcon)
}
NOTICE: 'getter for position: Int' is deprecated. Deprecated in Java.
And, final step, we need to write this, inside bindViewHolder:
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.bind(settingsList[position])
}
I am using recyclerView to show data from firebase database and I want to handle clicks,
Now the important part is that I want to know the number that was clicked in order to test google play in app billing before showing the next activity
I mean user should click item number one then pay to see information number 1 and so on
Any help, please ?
//my adapter
class MyAdapter(
private val arrayList: ArrayList<Long>
) :
RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view =
LayoutInflater.from(parent.context)
.inflate(R.layout.layout_item, parent, false)
return MyViewHolder(view)
}
override fun getItemCount() = arrayList.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.number.text = arrayList[position].toString()
}
class MyViewHolder(view: View) :
RecyclerView.ViewHolder(view) {
val number = view.findViewById<View>(R.id.singleNumberId) as TextView
}
}
Here is a small example I have of registering a click for a RecyclerView adapter item:
class PatientListAdapter : ListAdapter<Patient, PatientListAdapter.PatientViewHolder>(co.za.abcdefgh.viewmodels.PatientListViewModel.DiffItemCallback) {
// this property will be used to set the onclick callback for the entire adpater
var onPatientSelectedCallback: PatientSelectedCallback? = null
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): PatientViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_patient, parent, false) as View
return PatientViewHolder(view)
}
override fun onBindViewHolder(holder: PatientViewHolder, position: Int) {
holder.view.item_patient_name.text = getItem(position).toString()
holder.view.item_patient_folderNumber.text = getItem(position).folderNumber
// lets set our on click for each viewholder here
holder.view.item_patient_info_card.setOnClickListener {
// the secret sauce .... getItem(holder.adapterPosition)
onPatientSelectedCallback?.onPatientSelected(getItem(holder.adapterPosition))
}
}
class PatientViewHolder(val view: View) : RecyclerView.ViewHolder(view)
// interface which defines a method signature that will called when a item in the adpater is selected
interface PatientSelectedCallback {
fun onPatientSelected(patient: Patient)
}
}
and then wherever you use the adapter after instantiating simply do:
val viewAdapter = PatientListAdapter()
viewAdapter.onPatientSelectedCallback =
object : PatientListAdapter.PatientSelectedCallback {
override fun onPatientSelected(patient: Patient) {
// do something with the chosen item
patientViewModel.setPatient(patient)
}
}
onBindViewHolder not called when use data binding on Recyclerview.
So, the Activity show blank white screen.
When I see on logcat, the list size and itemCount is 20 but onBindViewHolder method never called.
class NewsAdapter : RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
private val listArticle = mutableListOf<Article>()
fun update(list: List<Article>) {
listArticle.addAll(list)
notifyDataSetChanged()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): NewsAdapter.ViewHolder {
return ViewHolder(viewGroup)
}
class ViewHolder(
private val parent: ViewGroup,
private val binding: AdapterNewsBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.adapter_news,
parent,
false
)
) : RecyclerView.ViewHolder(binding.root) {
fun bind(article: Article) {
binding.article = article
}
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
Log.d("NewsAdapter", "onBindViewHolder")
viewHolder.bind(listArticle[position])
}
override fun getItemCount() = listArticle.size
}
How to call onBindViewHolder in Recyclerview with data binding?
So I can show the data on Activity.
Solved
add apply plugin: 'kotlin-kapt' in your app gradle
So, you can make static method like this on your adapter
companion object {
#BindingAdapter("items")
#JvmStatic
fun RecyclerView.bindItems(items: List<Article>?) {
val adapter = adapter as NewsAdapter
if (!items.isNullOrEmpty()) adapter.update(items)
}
}
Done
I have a problem with:
RecyclerView: No adapter attached; skipping layout
I can't define adapter in OnCreate because the list is not ready.
How I can define adapter in OnCreate? or what is a possible solution for resolve my problem?
In onCreate I did:
adapter = MyAdapter(this#MyActivity)
adapter.data = ArrayList()
Then later I just set adapter.date = xxx
In my adapter I have:
class MyAdapter(val activity: MyActivity) :
RecyclerView.Adapter<MyAdapter.BodyViewHolder>() {
var data: MutableList<MyModel>? = null
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = data?.size ?: 0
fun ViewGroup.inflate(#LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BodyViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.my_layout, parent, false)
return BodyViewHolder(view)
}
override fun onBindViewHolder(holder: BodyViewHolder, position: Int) {
holder.bindValue(data?.get(position), activity)
}
class BodyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindValue(record: MyModel?, activity: MyActivity) {
record?.let {
itemView.mTextView.text = ....
}
}
}
}
Worth mentioning that the height of my recycler view is wrap_content
<android.support.v7.widget.RecyclerView
android:id="#+id/mRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
It's totally ok if you don't have data in onCreate. What you need to do is to define the adapter and bind it with your RecyclerView. Once you have data ready, add data to the list in adapter and notify it. Example as below
class MyAdapter: RecyclerView.Adapter<MyViewHolder>() {
private val data = mutableListOf<MyModel>()
override fun getItemCount() = data.count()
fun addData(data : MyModel) {
// add single data the list or call addAll() to add a group of data.
// just remember not to replace the variable
}
fun clearData() {
}
fun deleteData(id: Int) {
}
}
class adpCoba(val context: Context, val datalist:ArrayList<dataCoba>):RecyclerView.Adapter<adpCoba.MyViewHolder>(){
class MyViewHolder (itemView:View):RecyclerView.ViewHolder(itemView){
val text :TextView=itemView.findViewById(R.id.textcoba)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(context).inflate(R.layout.fetch_coba,parent,false)
return MyViewHolder(itemView)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currntItem = datalist [position]
holder.text.text= currntItem.nama
}
override fun getItemCount(): Int {
return datalist.size
}
}