i have a problem the postion value is lost when sending it to viewmodel
class itemAdapter_Fruit (private val context:fruit , val dataset:List<FruitsType>):RecyclerView.Adapter<itemAdapter_Fruit.ViewHolder>() {
class ViewHolder (view: View):RecyclerView.ViewHolder(view){
val fruitImage:ImageView = view.findViewById(R.id.fruit_image)
val fruittext:TextView = view.findViewById(R.id.Fruit_name_text)
init {
view.setOnClickListener{
val position =adapterPosition
datamodel().setpos(position)
view.findNavController().navigate(R.id.action_fruit2_to_clicked_Item)
}
}
}
override fun onCreateViewHolder (parent: ViewGroup, viewType: Int): itemAdapter_Fruit.ViewHolder {
val adapterlayout = LayoutInflater.from(parent.context).inflate(R.layout.fruits_list,parent,false)
return ViewHolder(adapterlayout)
}
override fun onBindViewHolder(holder: itemAdapter_Fruit.ViewHolder, position: Int) {
val item = dataset[position]
holder.fruitImage.setImageResource(item.ImageFruitId)
holder.fruittext.text=context.getText(item.FruitNameId)
}
override fun getItemCount(): Int {
return dataset.size
}
}
when i trying to send the data to viewmodel from recycler adapter it's lost
Related
I try to show data form api in Custom recycler view by using view binding and diffUtil
I try to use DiffUtil with Binding view in RecyclerView Adapter
but when I send the data from fragment differ.submitList(the DATA)
nothing the show in the list
when I debugging I found the data come from API and go to differ
class NewsAdapter : RecyclerView.Adapter<NewsAdapter.ItemViewHolder>() {
private val differCallBack = object : DiffUtil.ItemCallback<Article>() {
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url == newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
val differ = AsyncListDiffer(this, differCallBack)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_article_preview, parent, false)
)
}
inner class ItemViewHolder(private val view: View) :
RecyclerView.ViewHolder(view) {
var binding =
ItemArticlePreviewBinding.bind(view)
fun bind(article: Article) {
Glide.with(view).load(article.urlToImage).into(binding.ivArticleImage)
binding.tvTitle.text = article.title
binding.tvSource.text = article.source.name
binding.tvDescription.text = article.description
binding.tvPublishedAt.text = article.publishedAt
}
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val article = differ.currentList[position]
holder.itemView.apply {
holder.bind(article)
setOnClickListener {
onItemClickListener?.let {
it(article)
}
}
}
}
private var onItemClickListener: ((Article) -> Unit)? = null
fun setOnClickListener(listener: (Article) -> Unit) {
onItemClickListener = listener
}
override fun getItemCount(): Int {
return differ.currentList.size
}
}
Inside ItemViewHolder, the itemView you are binding data to, and the itemView you are passing to the recyclerView are two different views.
The view which you have passed to ItemViewHolder after creating from onCreateViewHolder is being passed to the RecyclerView, but you are binding data to a different view that you are inflating using the viewBinding of the ItemArticlePreviewBinding.
You can change ViewHolder's constructor to accept a binding instance instead of a view and then bind data to that binding object.
inner class ItemViewHolder(private val binding: ItemArticlePreviewBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(article: Article) {
Glide.with(view).load(article.urlToImage).into(binding.ivArticleImage)
binding.tvTitle.text = article.title
binding.tvSource.text = article.source.name
binding.tvDescription.text = article.description
binding.tvPublishedAt.text = article.publishedAt
}
}
Inside onCreateViewHolder inflate a ViewBinding object and pass that to the ItemViewHolder's constructor
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(
ItemArticlePreviewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
How should I initialize viewHolder? I have this error:
What I need to do is to get selected item in recyclerView but without using onClick method. When I get this selected item I need to show Toast message. Item is data class. Is it possible to pass some value from adapter to activity? Like I need to pass actual items from Data Class.
Process: com.pors.coopreaderlast, PID: 7862
kotlin.UninitializedPropertyAccessException: lateinit property viewHolder has not been initialized
at com.pors.coopreaderlast.features.polozka.PolozkaAdapter.getViewHolder(PolozkaAdapter.kt:18)
at com.pors.coopreaderlast.features.polozka.PolozkaAdapter.getCurrentItem(PolozkaAdapter.kt:46)
at com.pors.coopreaderlast.features.polozka.PolozkaActivity.onStart(PolozkaActivity.kt:213)
this is for line where viewHolder is set in Adapter:
lateinit var viewHolder: PolozkaViewHolder
This is Adapter
class PolozkaAdapter(val chosen_item: Int, private val listener: OnItemClickListener): ListAdapter<Polozka, PolozkaAdapter.PolozkaViewHolder>(DiffCallback()){
var selectedItemPosition: Int = chosen_item
lateinit var viewHolder: PolozkaViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PolozkaViewHolder {
val binding = PolozkyItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
//return PolozkaViewHolder(binding)
viewHolder = PolozkaViewHolder(binding)
return viewHolder
}
override fun onBindViewHolder(holder: PolozkaViewHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
if (selectedItemPosition == position){
holder.itemView.setBackgroundColor(Color.parseColor("#DA745A"))
} else
{
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
}
}
fun getCurrentItem(): Polozka = super.getItem(viewHolder.bindingAdapterPosition)
override fun getItemId(position: Int): Long {
return super.getItemId(position)
}
override fun getItemCount(): Int {
return super.getItemCount()
}
inner class PolozkaViewHolder(private val binding: PolozkyItemBinding): RecyclerView.ViewHolder(binding.root){
init {
binding.root.setOnClickListener{
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION){
val item = getItem(position)
if (item != null){
listener.onItemClick(item, position)
}
}
notifyItemChanged(selectedItemPosition)
selectedItemPosition = bindingAdapterPosition
notifyItemChanged(selectedItemPosition)
}
}
fun bind(polozkaPolozka: Polozka){
binding.apply {
tvREG.text = polozkaPolozka.reg
tvVB.text = polozkaPolozka.veb.toString()
}
}
}
interface OnItemClickListener{
fun onItemClick(polozkaDoklad: Polozka, position: Int)
}
class DiffCallback: DiffUtil.ItemCallback<Polozka>(){
override fun areItemsTheSame(oldItem: Polozka, newItem: Polozka) =
oldItem.pvp06pk == newItem.pvp06pk
override fun areContentsTheSame(oldItem: Polozka, newItem: Polozka) =
oldItem == newItem
}
}
This is onCreate method but it can be in onCreate method also.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityPolozkaBinding.inflate(layoutInflater)
idPositionItem = intent.getIntExtra("positionItem",0)
val itemAdapter = PolozkaAdapter(idPositionItem, this)
binding.apply {
recyclerView.apply {
adapter = itemAdapter
layoutManager = LinearLayoutManager(this#ItemActivity)
}
itemViewModel.getall(index,idExp.toString() ).observe(this#PolozkaActivity){
itemAdapter.submitList(it)
}
}
val selectedItem = itemAdapter.getCurrentItem()
Toast.makeText(this, "Reg vybrane polozky je ${selectedItem.reg}", Toast.LENGTH_LONG).show()
I have similar question here: Similar question but here I use binding.
The reason you're getting this exception is that viewHolder has not been initalized at the moment you want to access it. And since it's a lateinit var, it expects to be initialized every time you access it. (see https://kotlinlang.org/docs/properties.html#late-initialized-properties-and-variables)
Instead of using a lateinit var for viewHolder, you can return the instance you created in the onCreateViewHolder() so no need to have an extra field in the adapter for it.
I believe you use viewHolder to find the selected item. In that case I suggest using a boolean (for instance you can name it selected) in your model object Polozka to indicate if the item is selected or not. And in you getCurrentItem() method I would write getCurrentList().find { it.selected } to find the currently selected item. In this case you need to update your list every time you select a new item and mark only that as selected.
class PolozkaAdapter(private val listener: OnItemClickListener): ListAdapter<Polozka, PolozkaAdapter.PolozkaViewHolder>(DiffCallback()){
lateinit var viewHolder: PolozkaViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PolozkaViewHolder {
val binding = PolozkyItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PolozkaViewHolder(binding)
}
override fun onBindViewHolder(holder: PolozkaViewHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
if (currentItem.selected){
holder.itemView.setBackgroundColor(Color.parseColor("#DA745A"))
} else
{
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
}
}
fun getCurrentItem(): Polozka = currentList.find { it.selected }
override fun getItemId(position: Int): Long {
return super.getItemId(position)
}
override fun getItemCount(): Int {
return super.getItemCount()
}
//Model object would look like
data class Polozka(
val selected: Boolean,
//rest of the fields
)
And in the observe you should do it like this.
itemViewModel.getall(index,idExp.toString() ).observe(this#PolozkaActivity){
// mark the item selected
val updatedList = it.mapIndexed { index, item ->
if (index == idPositionItem) {
item.copy(selected = true)
} else {
item
}
}
itemAdapter.submitList(updatedList)
}
}
val selectedItem = itemAdapter.getCurrentItem()
Toast.makeText(this, "Reg vybrane polozky je ${selectedItem.reg}", Toast.LENGTH_LONG).show()
And every time a new item gets selected you need to update your list.
I am working a RecyclerView item that has two views in it: a TextView and an ImagaView. I want to be able to click on both of them to cary out different functionalities.
How can I go about this
Here is my Adapter
class AddMeasurementAdapter(private val currentList:MutableList<DressMeasurementModel>, private val listener1: RecyclerClickListener, private val listener2: RecyclerClickListener): RecyclerView.Adapter<AddMeasurementAdapter.CardViewHolder>() {
//inner class
inner class CardViewHolder (itemView : View):RecyclerView.ViewHolder(itemView), View.OnClickListener{
val display:TextView = itemView.findViewById(R.id.measurement_recyclerview_item)
//Binding the data with the view
fun bind(dressMeasurementModel: DressMeasurementModel){
display.text = "${dressMeasurementModel.measurementName} ${dressMeasurementModel.measurement}"
}
init {
itemView.setOnClickListener(this#CardViewHolder)
}
override fun onClick(v: View?) {
val position: Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener1.onItemClick1(position, currentList)
}
if (position != RecyclerView.NO_POSITION) {
listener2.onItemClick2(position, currentList)
}
}
}
//Creating view
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.measurement_fragment_recyclerview_items, parent, false)
return CardViewHolder(view)
}
//Binding the view
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
holder.bind(currentList[position])
}
//Getting the item cout size
override fun getItemCount(): Int {
return currentList.size
}
}
Here is my interface
interface RecyclerClickListener {
fun onItemClick1(position: Int, currentList: MutableList<DressMeasurementModel>)
fun onItemClick2(position: Int, currentList: MutableList<DressMeasurementModel>)
}
You can add a listener in createViewHolder such as
//Creating view
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.measurement_fragment_recyclerview_items, parent, false)
view.textView.onClick{}
view.image.onClick{}
return CardViewHolder(view)
}
Okay, here is the Kotlin version of the answer.
In your bind method of viewholder,
fun bind(dressMeasurementModel: DressMeasurementModel) {
...
var position = adapterPosition
display.setOnClickListener {
listener.onTextClick(position)
}
image.setOnClickListener {
listener.onImageClick(position)
}
...
}
I later figured it So i want to share how i did it.
class AddMeasurementAdapter(
private val currentList: MutableList<DressMeasurementModel>,
private val listener1: RecyclerClickListener,
private val listener2: RecyclerClickListener
) : RecyclerView.Adapter<AddMeasurementAdapter.CardViewHolder>() {
// inner class
inner class CardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val display: TextView = itemView.findViewById(R.id.measurement_recyclerview_item)
val delete: ImageView = itemView.findViewById(R.id.measurementment_recyclerview_item_delete_button)
// Binding the data with the view
fun bind(dressMeasurementModel: DressMeasurementModel) {
display.text = "${dressMeasurementModel.measurementName} ${dressMeasurementModel.measurement}"
}
}
// Creating view
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.measurement_fragment_recyclerview_items,
parent,
false
)
return CardViewHolder(view)
}
// Binding the view and attaching the listener
override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
holder.bind(currentList[position])
holder.display.setOnClickListener {
listener1.onItemClickToEdit(holder.adapterPosition, currentList)
}
holder.delete.setOnClickListener {
listener2.onItemClickToDelete(holder.adapterPosition, currentList)
}
}
// Getting the item cout size
override fun getItemCount(): Int {
return currentList.size
}
}
When i assigned the onClickListener inside the fun bind(), the listener covered the the whole itemView layout and not the individual view inside the layout, hence only one click listener was achieved. The same thing applied inside the inner class.
Here is the interface
interface RecyclerClickListener {
fun onItemClickToEdit(position: Int, currentList: MutableList<DressMeasurementModel>)
fun onItemClickToDelete(position: Int, currentList: MutableList<DressMeasurementModel>)
}
By extending the interface in either activity of fragment, you have your onclickListener.
This article helped https://antonioleiva.com/recyclerview-listener/
So I created a RecyclerView to generate beverage items and for some reason my recycler veiw keeps on repeating the first item.
I tried using
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
while using
holder.itemView.text_view1.text = user.name
holder.itemView.text_view2.text = user.name
holder.itemView.text_View3.text = user.name
and the the results are the same.
What's causing the repetition?
BeverageAdapter.kt
class BeverageAdapter(val List:ArrayList<Beverage>)
RecyclerView.Adapter<BeverageAdapter.ViewHolder>() {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textViewName2 = itemView.findViewById(R.id.text_view1) as TextView
val texViewName3 = itemView.findViewById(R.id.text_view2) as TextView
val textViewHello = itemView.findViewById(R.id.text_View3) as TextView
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_activity, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return List.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val user: Beverage = List[position]
holder.textViewName2.text = user.name
holder.texViewName3.text = user.name
holder.textViewHello.text = user.name
}
}
Beverage.kt
data class Beverage(val name: String)
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.item_recycler_view)
recyclerView.setLayoutManager(LinearLayoutManager(this))
val users = ArrayList<Beverage> ()
users.add(Beverage("Coke"))
users.add(Beverage("Gingerale"))
users.add(Beverage("Rootbeer"))
val adapter = BeverageAdapter(users)
recyclerView.adapter = adapter
}
}
Don't use 3 different TextView .
Just use single TextView and set it in Adapter.
RecyclerView.Adapter<BeverageAdapter.ViewHolder>() {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textViewName1 = itemView.findViewById(R.id.text_view1) as TextView
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_activity, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return List.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val user: Beverage = List[position]
holder.textViewName1.text = user.name
}
}
In RecyclerView basically you create one view and it repeats as many as data is there.
How can I create on click event using interface?
In my application I've created view click interface to detect clicking on adapter items into parent activity. After creating interface and method into adapter how I can use this interface to call the view listener ?
Please check this code, It's working fine for me.
First Create Adapter class.
class ChapterAdapter(private val activity: Activity, val mWords: ArrayList<Chapter>, val btnlistener: BtnClickListener) : RecyclerView.Adapter<ChapterAdapter.ViewHolder>() {
companion object {
var mClickListener: BtnClickListener? = null
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolder(layoutInflater.inflate(R.layout.layout_capter_raw, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
mClickListener = btnlistener
val item = mWords[position]
holder.layout_chapter_name.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
if (mClickListener != null)
mClickListener?.onBtnClick(position)
}
})
}
override fun getItemCount(): Int {
return mWords.size
}
override fun getItemId(position: Int): Long {
return super.getItemId(position)
}
override fun getItemViewType(position: Int): Int {
return super.getItemViewType(position)
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val txt_capter_name = view.txt_capter_name
}
open interface BtnClickListener {
fun onBtnClick(position: Int)
}
}
After create and declare adapter in your Activity or Fragment.
listAdapter = ChapterAdapter(activity, _arrChapterList, object : ChapterAdapter.BtnClickListener {
override fun onBtnClick(position: Int, chapter_id: String, chapter_size: String, chapter_name: String) {
toast(chapter_id + " = " + chapter_size, Toast.LENGTH_LONG)
}
})
In Kotlin the proper way doing this, is using callbacks instead of Java Interfaces. Example:
class MyAdapter(private val callback: (YourModel) -> Unit) {
override fun onBindViewHolder(holder: DataBoundViewHolder<YourModel>, position: Int) {
bind(holder.binding, items!![position])
holder.binding.executePendingBindings()
holder.binding.root.setOnClickListener { callback(binding.model) }
}
}
And create the adapter somewhere using
MyAdapter myAdapter = MyAdapter( { println{"Clicked $it"} })
Edit: Since the Asker would like to see a full working code i used the code from Sumit and replaced the Interfaces with Kotlin-Callbacks.
class ChapterAdapter(private val activity: Activity,
val mWords: ArrayList<Chapter>,
val callback: (Any) -> Unit) :
RecyclerView.Adapter<ChapterAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
return ViewHolder(layoutInflater.inflate(R.layout.layout_capter_raw, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
val item = mWords[position]
holder.layout_chapter_name.setOnClickListener( callback {$it})
}
override fun getItemCount(): Int = mWords.size
override fun getItemId(position: Int): Long = super.getItemId(position)
override fun getItemViewType(position: Int): Int = super.getItemViewType(position)
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val txt_capter_name = view.txt_capter_name
}
and finally creating the Adapter
listAdapter = ChapterAdapter(activity, _arrChapterList, {
toast( "Clicked $it", Toast.LENGTH_LONG)
})
I have the same problem, and here is my solution:
package adapter
class MarketplaceListAdapter : RecyclerView.Adapter<MarketplaceListAdapter.ViewHolder> {
private val marketplaceList: ArrayList<Marketplace>
constructor(marketplaceList: ArrayList<Marketplace>) : super() {
this.marketplaceList = marketplaceList
}
private var listener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// implementation
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// implementation
}
override fun getItemId(position: Int): Long {
return 0
}
override fun getItemCount(): Int {
return marketplaceList.size
}
fun setListener(listener: OnItemClickListener) {
this.listener = listener
}
interface OnItemClickListener {
fun onItemClick(marketplace: Marketplace)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
var tvTitle: TextView = itemView.findViewById(R.id.tvTitle)
var ivIcon: ImageView = itemView.findViewById(R.id.ivIcon)
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View) {
listener?.onItemClick(marketplaceList[adapterPosition])
}
}
}
And on my fragment:
val list = view.findViewById<RecyclerView>(R.id.list)
list?.let {
adapter?.let { adapter ->
list.adapter = adapter
adapter.setListener(object : MarketplaceListAdapter.OnItemClickListener {
override fun onItemClick(marketplace: Marketplace) {
onMarketplaceSelected(marketplace)
}
})
}
}