Can't access view elements by LayoutInflater in Kotlin - android

I simply made an adapter for my recyclerview.like this :
class CustomerAdapter(mCtx:Context,val customers:ArrayList<Customer>):RecyclerView.Adapter<CustomerAdapter.ViewHolder>(){
and i have a xml layout file to inflate recycleview items.
But the problem is in onCreateViewHolder ... I can not access inflated elements ...
codes writed in my onCreateViewHolder method :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomerAdapter.ViewHolder {
val v = LayoutInflater.from(parent.getContext()).inflate(R.layout.lo_customers, parent, false);
var txt = v.id
}
i can not access view elements by id .

Even if you capture the View, you cannot directly access the children's in the View. In-order to get hold of children's you need to use
findViewById(R.id.my_textView_id) on the View object.
Hence replace your v.txt by v.findViewById(R.id.my_text_view_id).

This is completely the wrong way to do it . onCreateViewHolder the function name itself says that it is meant only for inflating the view and doing nothing else. It should only be used for creating the ViewHolder . Even if you inflate your view and assign view from there , it is going to crash in the future . You have to inflate the view there and pass the parent view to another class which should extend RecyclerView.ViewHolder() .
So do it the following way :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomerAdapter.ViewHolder {
val v = LayoutInflater.from(parent.getContext()).inflate(R.layout.lo_customers, parent, false);
}
Create an ViewHolder Class extending RecyclerView.ViewHolder :
class CustomViewHolder(val view: View) :
RecyclerView.ViewHolder(view) {
fun bind(model : Model) {
//Now here you can access all the components present in your layout in the following way and do the stuff you want with them:
val text = view.findViewById(R.id.viewid)
}
}

At gradle app
plugins {
id 'kotlin-android-extensions'
}
Or
apply plugin: 'kotlin-android-extensions'
Then your Activity Import this
import kotlinx.android.synthetic.main.activity_main.*

Try not to access from android.R, rather try like this:
CategoryAdapterHolder(LayoutInflater.from(parent.context).inflate(com.example.secondhand.R.layout.category_list, parent, false))

Related

Not able to access a textview by findviewbyId method in my created kotlin file from a xml file (not Main_Activity.xml)

I am new to Android Development. And I have written the code for the Recycler view for the first time.
I am building the Affirmations App with the help of the Android Developer Training Program. My Code is completely the same as the stated solution code of this codelab. But I am not able to access the layout file as well as the textview in it, in my ItemAdapter.kt file. Below is the code
package com.example.affirmations.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.model.Affirmation
/**
* Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
*/
class ItemAdapter(
private val context: Context,
private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
// Each data item is just an Affirmation object.
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(com.example.affirmations.R.id.item_title)
}
/**
* Create new views (invoked by the layout manager)
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(com.example.affirmations.R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
/**
* Replace the contents of a view (invoked by the layout manager)
*/
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)
}
/**
* Return the size of your dataset (invoked by the layout manager)
*/
override fun getItemCount() = dataset.size
}
I am facing error at line no. 26 (R.id.item_title) and 36 (R.layout.list_item)
Code of list_item.xml file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</resources>
On running the app it gives error like this
Can't determine type for tag '<TextView android:id="#+id/item_title" android:layout_height="wrap_content" android:layout_width="wrap_content" xmlns:android="http://schemas.android.com/apk/res/android"/>'
Thank you!! in advance
change your ItemViewHolder class and onCreateViewHolder function to
class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
and
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
// create a new view
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)
return ItemViewHolder(adapterLayout)
}
You should be importing your list_item from resource(R) folder.
You are using: (com.example.affirmations.R.layout.list_item, parent, false)
BUT
you should use (R.layout.list_item, parent, false)
The Problem is Solved. I just need to create the list_item.xml file in the layout directory. I created it just randomly in the resources folder.

viewholder init for onItemClick is not working

I am researching on how to add an onClick event on my recyclerview properly,
currently I am using the interface inside my customAdapter
class CategoryAdapter(val categoryList : List<CategoryObject>, val context: Context, val mItemClickListener: MainInterface) : RecyclerView.Adapter<CategoryAdapter.ViewHolder>() {
interface MainInterface {
fun onCategoryItemClick(categoryKey: Int)
}
override fun getItemCount(): Int {
return categoryList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.listview_category, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder?.categoryName?.text = categoryList[position].categoryName
// holder?.categoryName.setOnClickListener{
// mItemClickListener.onCategoryItemClick(position)
// }
// holder?.categoryName?.setOnClickListener { listener(categoryList[position]) }
}
inner class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val categoryName = view.lv_txt_category_name
init {
view.setOnClickListener {
mItemClickListener.onCategoryItemClick(adapterPosition)
}
}
}
}
viewHolder's onClick does not register on my activity ovveride function.
but putting the onClick inside onBindViewHolder works perfectly,
I'm not sure which is more efficient, if the onBindViewHolder onClick is the right answer, then I'll stick with it, but if the viewHolder is the right one, why it does not work properly?
Thanks in advance!
Update
This is the stackoverflow post I'm using to research things
RecyclerView itemClickListener in Kotlin
tried android:clickable="true" in your xml?
As the documentation suggest for:
RecyclerView.Adapter.html#onBindViewHolder
Called by RecyclerView to display the data at the specified position.
This method should update the contents of the itemView to reflect the
item at the given position.
And for the RecyclerView.Adapter.html#onCreateViewHolder
Called when RecyclerView needs a new RecyclerView.ViewHolder of the
given type to represent an item.
This new ViewHolder should be constructed with a new View that can
represent the items of the given type. You can either create a new
View manually or inflate it from an XML layout file.
As onCreateViewHolder is a method where we return the type of ViewHolder only while in onBindViewHolder we update or initialise the data to display.
It means no data is binded in onCreateViewHolder and binding anything in this method will have no effect.

Casting in RecyclerView Adapter?

May I know which one is better?
The first one we set the textView as TextView and we inflate the view and cast it to TextView type
The second one we set the textView as View type and we inflate it as type View
class MyViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyAdapter.MyViewHolder {
val textView = LayoutInflater.from(parent.context)
.inflate(R.layout.my_text_view, parent, false) as TextView
return MyViewHolder(textView)
}
or
class MyViewHolder(val textView: View) : RecyclerView.ViewHolder(textView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyAdapter.MyViewHolder {
val textView = LayoutInflater.from(parent.context)
.inflate(R.layout.my_text_view, parent, false)
return MyViewHolder(textView)
}
Like everything, it depends on more conditions. If you are using view holder like this, I mean, not using custom XML, it is better to use View, because if in any place you don't need any specific TextView property, then there is no point of using TextView.
But! If you want to use your custom XML file and use this view inside ViewHolder as TextView, then it should TextView since the beginning.

Out-projected type prohibits the use of method

I can't seem to wrap my head around Kotlin generics, please help
I read here and here and I still can't determine how to make this work. I have a RecyclerView adapter that uses an abstracted parent BaseFieldVH as the ViewHolder class.
So the Adapter class looks like this:
class MyAdapter() : RecyclerView.Adapter<BaseFieldVH<*>>() {
...
override fun onBindViewHolder(holder: BaseFieldVH<*>, position: Int) {
holder.bind(data[position])
}
}
And the ViewHolder implementation looks like this:
abstract class BaseFieldVH<in FF : BaseFormField>
(itemView: View) : RecyclerView.ViewHolder(itemView) {
...
abstract fun bind(formField: FF)
}
But the actual call to holder.bind(data[position]) is displaying the error:
Out-projected type 'BaseFieldVH<*>' prohibits the use of 'public
abstract fun bind(formField: FF): Unit defined in ...
use in Nothing?
I've also tried defining the Adapter with BaseFieldVH<in Nothing> but then I get a Type Mismatch error for attempting to put Nothing into a function that requires a BaseFormField.
use BaseFormField
Defining the Adapter with BaseFieldVH<iBaseFormField> actually resolves the binding issue, but then in the onCreateViewHolder there is a type mismatch for the view holders:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseFieldVH<BaseFormField> {
val itemView: View
when (viewType) {
HEADER_TYPE -> {
itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.item_header, parent, false)
return HeaderVH(itemView)
}
HeaderVH is an extension of BaseFieldVH, and so there's a type mismatch
It looks like the answer to this issue is that you can't implement a ViewHolder class that specifies a generic for binding. My guess is this arises from the fact that the RecyclerView.Adapter is written in Java and any Kotlin extension is too restrictive to implement it properly.
If you can contradict this conclusion, let me know!
I found this post which explains the whole projection concept in a way that I could understand.
In the end I abandoned my attempt to control the Type for the ViewHolder:
abstract class BaseFieldVH
(itemView: View) : RecyclerView.ViewHolder(itemView) {
...
abstract fun bind(formField: Any)
}
This means each child class of BaseFieldVH has to do some a type check in its bind function. For instance:
class HeaderVH
(itemView: View) : BaseFieldVH(itemView) {
...
override fun bind(headerField: Any) {
if (headerField is HeaderField) {
// do something
}
}
}
But now at least I'm not getting any errors.
If you have multiple complex objects in data list (in order to show different items in the recycler view) you also need to specify you holder.
The idea here is that you provide Adapter, not a Holder to decide how you need to handle each object type.
class MyAdapter() : RecyclerView.Adapter<BaseFieldVH<*>>() {
...
override fun onBindViewHolder(holder: BaseFieldVH<*>, position: Int) {
val item = data[position]
holder as BaseFieldVH<DATA_ITEM_TYPE>
item as DATA_ITEM_TYPE
holder.bind(item)
}
}

Encapsulate ViewHolder Functionality for reuse

I have an application with a news feed. I am using a recycler view to populate my news feed. I have 10 variations of viewholders for the recycler view depending on what type of content received from the server.
That all works fine.
My problem is when the user clicks on the item in the recycler view the user is brought to a new fragment, with the content and all the comments below.
My issue is this is a new fragment, so I'm outside of the recycler view. In this case I have to inflate the layout of the item clicked on in the recyclerview and that's fine. However, it should have all the functionality the item in the feed as, so for example, play media (video, audio), navigate to new fragments.
My question, is it possible to someway encapsulate the functionality of the viewholder in the recyclerview? Otherwise the same functionality will be repeated.
Any help is appreciated.
I've attached a drawing to help illustrate.
class VideoViewHolder(
private binding: VideoItemBinding
) : RecyclerView.ViewHolder(binding.root){
companion object {
fun create(
inflater: LayoutInflater,
viewGroup: ViewGroup
): VideoViewHolder {
val binding = DataBindingUtil.inflate<VideoItemBinding>(
inflater, R.layout.item_video, viewGroup, false
)
return VideoViewHolder(binding)
}
}
fun bind(video: Video){
// bind view to video model
// set click listeners
// fire network request on click events and on response check if the viewholder is binded to same model else its has been reused.
}
}
From Video Adapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return VideoViewHolder.create(inflater, parent)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is VideoViewHolder -> {
holder.bind(item as Video, doubtsRepo)
}
}
}
To reuse in activity/fragment/ container layout without adapter manually create viewholder and call its bind function
val vh = VideoViewHolder.create(inflater, parent)
// get view from vh.binding.root and manually add it to view container and then bind the model to the view to reuse view holder functionality without any changes.
vh.bind(video)

Categories

Resources