please I'm trying to dynamically add a textitem via an EditText by clicking a button in a Recyclerview, but when I click the button to add the text in the Array related to the Adapter and I apply the adapter.notifyDataSetCh.anged() the recycleview does not update and no longer detects click
mainActivity.kt
ackage com.example.recycleviewwork
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.view.View.OnClickListener
import android.widget.Button
import android.widget.EditText
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class Travel : AppCompatActivity(), View.OnClickListener {
var check_travel_list = arrayOf<String>("cni", "psp", "hhn", "ddp", "ppe", "leh")
var state_check_bool = arrayOf<Boolean>(false, false, false, false,false,false)
val adapter = travel_list_adapter(check_travel_list,state_check_bool, this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_travel)
var textEditValue = ""
// RECYCLEVIEW Travel page
val recview_travel = findViewById<RecyclerView>(R.id.travel_recview)
recview_travel.layoutManager = LinearLayoutManager(this)
recview_travel.adapter = adapter
//EDIT text
val edit_text_view = findViewById<EditText>(R.id.textedit)
// ADD travel button
val button_add_travel = findViewById<Button>(R.id.button_travel)
button_add_travel.setOnClickListener()
{
textEditValue = edit_text_view.text.toString()
check_travel_list = textEditValue
state_check_bool = false
adapter.notifyDataSetChanged()
println(textEditValue)
}
}
override fun onClick(vue: View) {
val index = vue.tag as Int
//changement des bool de couleur
if(index != null)
{
state_check_bool[index] = !state_check_bool[index]
adapter.notifyDataSetChanged()
}
}
}
Adapter.kt
package com.example.recycleviewwork
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
class travel_list_adapter(val travel_check_list : Array <String>,val travel_state_check : Array<Boolean> , val travel_click : View.OnClickListener) : RecyclerView.Adapter<travel_list_adapter.ViewHolder>()
{
class ViewHolder (itemview: View ): RecyclerView.ViewHolder(itemview)
{
val cartview = itemview.findViewById<CardView>(R.id.cartview_travel_list)
val text_check = itemview.findViewById<TextView>(R.id.text_travel_list)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): travel_list_adapter.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view_item = inflater.inflate(R.layout.travel_list,parent,false)
return ViewHolder(view_item)
}
override fun getItemCount(): Int {
return travel_check_list.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val current_check = travel_check_list[position]
val state_state_check = travel_state_check[position]
holder.cartview.setOnClickListener(travel_click)
holder.cartview.tag = position
holder.text_check.text = current_check
if(state_state_check == true)
{
holder.text_check.setTextColor(Color.parseColor("#8BC34A"))
}
else
{
holder.text_check.setTextColor(Color.parseColor("#000000"))
}
}
}
However, by changing the value of a text in the Array linked to Adapter without changing the array lenght , the recycleview displays the change normally.
CodePudding user response:
You're not updating the array in your Activity
, you're replacing it with =
. That creates a new array with the extra element added, and assigns that to your check_travel_list
variable.
You start off with the first array object, and you pass that to your Adapter
so both the Activity and Adapter are looking at the exact same object. If you made changes to that object, both places would see it. That's why changing the text on an existing element works - you're modifying the Array
that the Adapter
is also using, so it sees that change.
But once you replace check_travel_list
in the Activity
with the second array object (created with =
) then they're no longer looking at the same thing. The Adapter
still holds a reference to the original array, which hasn't changed.
So two things - first, use a List
instead of an Array
. They're way easier to work with, you can add and remove items without needing to worry about empty spaces or the length (this is why adding another element creates a whole new array - arrays are fixed-length) and they're just the idiomatic way of holding a sequence of items in Kotlin. Only use an array if you actually need one for a specific reason (e.g. you do want the empty spaces*.
With a list, you could just do this:
// needs to be a MutableList if you need to modify it
val check_travel_list = mutableListOf("cni", "psp", "hhn", "ddp", "ppe", "leh")
...
check_travel_list.add("something else")
Now you're modifying the existing list object, and your Adapter
(which is looking at the same list) will see the change too.
Also note that check_travel_list
is a val
, which means it can't have a new list assigned to it - this avoids bugs like the one you're getting. By making it val
by default, if you do decide to change that to a var
you need to think about why you need to reassign it with different list object, and it forces you to think about the consequences of doing that. Using val
unless you need to modify it is safer, same as with the List
/MutableList
thing!
The other thing is that I'd recommend not sharing the list in the first place! When you have two completely separate things sharing the same data object, it can get confusing when one of them starts modifying it, or makes changes it expects the other user to see, but with no clear way of pushing those changes.
So it's safer to keep them completely separate. Instead of sharing a list object, let the Adapter
manage its own state. Create a function like fun setData(data: List<String>)
that updates the Adapter's own internal data, and runs the appropriate notify*
function itself. Nothing on the outside should be concerned with what's happening in the Adapter internally, y'know?
// passing in initial data - note this isn't a property (val/var), it's just temporary
class MyAdapter(initialData: List<String>) {
// this is what actually holds the adapter's data
var internalData: List<String> = emptyList()
init {
// initialise the internal data set
setData(initialData)
}
fun setData(data: List<String>) {
// calling toList() makes a new copy of the list, that can't be modified
// by anything externally - you can only update the adapter data through this function!
internalData = data.toList()
// now update using an appropriate method - the adapter can be smart about it
// depending on what's changed (or it could be using a DiffUtil)
notifyDataSetChanged()
}
}
Then you update the RecyclerView
just by calling setData
with your updated data. And that way it doesn't matter if it's the same list, or you made a new copy, or whatever - all the adapter cares about is getting some data and updating itself to store and display it.
You don't need the initialData
and init
stuff if you don't want, you could just call setData
after creating the Adapter
object to initialise it. But this is how you'd do it during construction if you really wanted!