RecyclerView items changes randomly on clicking an item - android

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

Related

RecyclerViewAdapter in Android Kotlin

I have an array that save different sensors data (in non-activity class) and I want the RecyclerView to be updated based on data of that array. Is it possible to automatically change the presented data?
I used setOnClickListener but nothing updated. The RecyclerView just display the default data. Also, I used text view however the data is updated with each click not continuously.
Thanks for your help.
In fragment:
override fun onClick(v: View?) {
when (v?.id) {
R.id.buttonStart-> {
start(v)
}
R.id.buttonStop-> {
stop(v)
}
}
}
fun start (_v: View) {
listListSensors.adapter = SensorsRecyclerViewAdapter(model.listSensors(), mListener!!)
}
In the other class:
fun listSensors(): Sensors {
return currentSensor
}
In recycle view:
class SensorsRecyclerViewAdapter (items: Sensors, listener: ListSensorsFragment.OnListFragmentInteractionListener)
: RecyclerView.Adapter<SensorsRecyclerViewAdapter.SensorsViewHolder>() {
private var mValues: Sensors = items
private var mListener: ListSensorsFragment.OnListFragmentInteractionListener = listener
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SensorsViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.recyclerviewlistsensors_item, parent, false)
return SensorsViewHolder(itemView)
}
override fun onBindViewHolder(holder: SensorsViewHolder, position: Int) {
holder.mItem = mValues
holder.listSensorsLightView.text = mValues.getLight()
holder.listSensorsTemperatureView.text = mValues.getTemperature()
holder.listSensorsGyroscopeView.text = mValues.getGyroscope()
holder.listSensorsAccelerometerView.text = mValues.getAccelerometer()
holder.listSensorsGravityView.text = mValues.getGravity()
holder.mView.setOnClickListener { mListener.onListFragmentInteraction(holder.mItem) }
}
in your Adapter
var itemClickListener: ((position: Int, name: String) -> Unit)? = null
fun setData(data:List<AnyList>){
this.list.clear()
this.list.addAll(data)
notifyDataSetChanged()
}
//bindviewholder
itemClickListner.invoke(1,"anyvalue")
// in fragment
adapter.itemClickListener = {
position, name ->
Toast.makeText(requireContext(),"position is $position name is $name ",Toast.LENGTH_SHORT).show()
}
// call setData() any place in fragment

How to use viewmodel in recyclerview adapter

I am creating Recyclerview using MVVM and data binding. Now I need to perform some network operation in Recycler view adapter. So how can we create ViewModel and Live data for adapter. How can adapter observe live data.
I have create ViewModel using activity context and but not working proper
class CartAdapter(cartList: ArrayList<ProductData>, context: BaseActivity) :
RecyclerView.Adapter<CartAdapter.MyViewHolder>() {
private val itemList = cartList
private val activity = context
private var viewModel: CartAdapterViewModel =
ViewModelProvider(context).get(CartAdapterViewModel::class.java)
init {
initObserver()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.cart_item_layout, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = itemList[position]
holder.setData(item)
}
override fun getItemCount(): Int {
return itemList.size
}
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var binding: CartItemLayoutBinding? = DataBindingUtil.bind(itemView)
fun setData(model: ProductData) {
binding?.item = model
}
}
private fun initObserver() {
viewModel.statusResponse.observe(activity, {
activity.hideLoader()
})
viewModel.serverError.observe(activity, {
activity.hideLoader()
})
}
}
You should not create a separate ViewModel for adapter.
The Classic way:
The adapter should expose an interface whose implementation would later handle e.g. clicks on an item in the RecyclerView.
class CartAdapter(
cartList: ArrayList<ProductData>,
private val itemClickListener: ItemClickListener // This is the interface implementation
// that will be provided for an item click in this example
) : RecyclerView.Adapter<CartAdapter.MyViewHolder>() {
interface ItemClickListener {
fun onItemClick(position: Int)
}
override fun onBindViewHolder(holder: ScanResultViewHolder, position: Int) {
holder.binding.root.setOnClickListener {
itemClickListener.onItemClick(position)
}
}
// this function would be useful for retrieving an item from the recyclerview
fun getItemAt(position: Int): ProductData = itemList[position]
...
}
Later on when instantiating the CartAdapter in Your Activity or Fragment You would have to provide that interface implementation:
private val cartAdapter: CartAdapter = CartAdapter(
cartList,
object : CartAdapter.ItemClickListener {
override fun onItemClick(position: Int) {
// this function will handle the item click on a provided position
doSomethingWithARecyclerViewItemFrom(position)
}
}
)
private fun doSomethingWithARecyclerViewItemFrom(position: Int) {
// get the adapter item from the position
val item = cartAdapter.getItemAt(position)
// later on You can use that item to make something usefull with Your ViewModel of an activity/fragment
...
}
This way the Adapter doesn't have to have any ViewModels - the corresponding actions on RecyclerView items can be handled by the Activity view model.
In my example this action is an item click but for a more specific action, You would have to update Your question with those details.
The more compact way:
You can implement the same functionality as above using even more compact and neat way by using function types:
class CartAdapter(
cartList: ArrayList<ProductData>,
private val itemClickListener: (productData: ProductData) -> Unit // notice here
) : RecyclerView.Adapter<CartAdapter.MyViewHolder>() {
override fun onBindViewHolder(holder: ScanResultViewHolder, position: Int) {
holder.binding.root.setOnClickListener {
itemClickListener(position) // slight change here also
}
}
}
My suggestion is using constructor to pass viewModel instance.
Without concerns of unhandled instance scope problem anyway.
Have a happy day.

android checkbox recyclerview using kotlin

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) }

How to get position of the item in RecyclerView

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) {
}
}

(Kotlin) Position of RecyclerView returns -1 when trying to click on an item

I'm new to Android development (and Kotlin).
I'm trying to implement a RecyclerView (which works fine) and when I click on a specific row it opens a new activity (Intent).
However, whenever I've press/click on one of the rows, I'm only able to get the value "-1" returned.
I've tried a number of different approaches (you should see the number of tabs in my browser).
This seems like it should be a fairly straightforward occurrence for something as common as a RecyclerView, but for whatever reason I'm unable to get it working.
Here is my RecyclerView Adapter file:
class PNHLePlayerAdapter (val players : ArrayList<PNHLePlayer>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
var onItemClick: ((Int)->Unit) = {}
// Gets the number of items in the list
override fun getItemCount(): Int {
return players.size
}
// Inflates the item views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(
R.layout.pnhle_list_item,
parent,
false
)
val viewHolder = ViewHolder(itemView)
itemView.setOnClickListener {
onItemClick(viewHolder.adapterPosition)
}
return ViewHolder(itemView)
}
// Binds each item in the ArrayList to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvPlayerName?.text = players[position].Name
holder.tvPlayerRank?.text = position.toString()
holder.tvPNHLe?.text = players[position].PNHLe.toString()
holder.tvTeam?.text = players[position].Team
holder.ivLeague?.setImageResource(leagueImageID)
}
}
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val linLayout = view.hor1LinearLayout
val ivTeam = view.teamImageView
val tvPlayerName = view.playerNameTextView
val tvPlayerRank = view.rankNumTextView
val tvPNHLe = view.pnhleTextView
val tvTeam = view.teamTextView
val ivLeague = view.leagueImageView
}
As you can see, there is a class property "onItemClick" which uses a lambda as the click callback.
I setOnClickListener in the onCreateViewHolder method after the view is inflated.
Next, in my Activity I add the list to my Adapter and set the call back.
However, every time I 'Toast' the position it is displayed as '-1'.
val adapter = PNHLePlayerAdapter(list, this)
adapter.onItemClick = { position ->
Toast.makeText(this, position.toString(),Toast.LENGTH_SHORT).show()
var intent = Intent(this, PlayerCardActivity::class.java)
//startActivity(intent)
}
rv_player_list.adapter = adapter
Perhaps I'm not thinking about this properly, but shouldn't the position represent the row number of the item out of the RecyclerView???
Ideally, I need to use the position so that I can obtain the correct item from the 'list' (ArrayList) so that I can pass information to my next Activity using the Intent
I found the issue.
Change this line in onCreateViewHolder:
return ViewHolder(itemView)
to this one:
return viewHolder
I would reorganize the adapter like this:
class PNHLePlayerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<Adapter.ViewHolder>() {
interface AdapterListener {
fun onItemSelected(position: Int?)
}
var players: List<Player> = listOf()
set(value) {
field = value
this.notifyDataSetChanged()
}
var listener: AdapterListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_car_selector, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
return brands.size
}
inner class ViewHolder(view: View): androidx.recyclerview.widget.RecyclerView.ViewHolder(view) {
private var position: Int? = null
private val baseView: LinearLayout? = view.findViewById(R.id.baseView) as LinearLayout?
...
init {
baseView?.setOnClickListener {
listener?.onManufacturerSelected(position)
}
}
fun bind(position: Int) {
this.position = position
...
}
}
}
And from your activity/fragment set the listener as adapter.listener = this, and implement the onItemSelected(position: Int?)
override fun onItemSelected(position: Int?) {
...
}

Categories

Resources