Lists pass by value? - android

I'm trying to make a copy of my list that contains arraylists inside of it, but when I edit the copied value, the original values gets changed, which means am passing my ref, I tried all sorts of methods like using the copy method for each item, or creating a list/mutable list from the original but it didn't work, so my question is how do you pass a value in kotlin instead of ref?
I made the original as val and its field too.
class FAQAdapter(val faqModel: MutableList<FAQSection>) : RecyclerView.Adapter<FAQAdapter.ViewHolder>() {
val faqOriginal: List<FAQSection>
var faqSectionsCopy: MutableList<FAQSection>
init {
faqOriginal = faqModel
faqSectionsCopy = faqModel.toMutableList()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.faq_section_item, parent, false))
}
override fun getItemCount(): Int {
return faqSectionsCopy.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.faqSectionHeading.text =
faqSectionsCopy.get(holder.adapterPosition).sectionheader
holder.questionsRecyclerView.layoutManager =
LinearLayoutManager(holder.itemView.context,
LinearLayoutManager.VERTICAL, false)
holder.questionsRecyclerView.adapter =
FAQQuestionsAdapter(faqSectionsCopy.get(holder.adapterPosition)
.faqQuestions)
holder.questionsRecyclerView.setHasFixedSize(true)
holder.questionsRecyclerView.minimumHeight = convertDpToPx(holder.itemView.context, 88) * faqSectionsCopy.get(holder.adapterPosition).faqQuestions.size
}
fun filter(text: String) {
var text = text.trim().toLowerCase()
// faqSectionsCopy.clear()
if (text.isEmpty()) {
// faqSectionsCopy = faqModel as ArrayList<FAQSection>
} else {
text = text.toLowerCase()
faqSectionsCopy.map {
faqSectionsCopy[0].faqQuestions = it.faqQuestions.filter { it.question.contains(text) } as ArrayList<FAQQuestion>
}
}
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var questionsRecyclerView = itemView.faqQuestionsRecyclerView
var faqSectionHeading = itemView.faqHeading
}
}

You cannot pass data by value in Kotlin or Java. Instead you should ensure to work on a copy when you retrieve the data:
fun withList(listOfLists: List<List<Any>>) {
...
val list = listOfLists[0].toMutableList()
...
}
The toMutableList extension function will then create a mutable copy of the list and therefore the original will not change. As both levels of the parameter are of type List, the values are immutable themself.

Actually every data pass in Java/Kotlin is by value. Except that in this situation you're passing a reference by value but not the list itself.
To pass a copy of list, you can
passed using yourList.toList() which will make a copy of the list and return an immutable list
explicitly create new mutable ArrayList(yourList)

Related

I am doing the Kotlin android basics course and I'm getting expecting member declaration on a simple line of code, I have no idea what to do

Hi I'm new to kotlin and I'm working on the words dictionary app I accidentally deleted a line of code but managed to put it back in and now for some reason it's not working. The error is expecting member declaration. I'm guessing it's linked to line 118 of the code expecting top level declaration, it's just a curly bracket. PLease help
/**
* Adapter for the [RecyclerView] in [DetailActivity].
*/
class WordAdapter(private val letterId: String, context: Context) :
RecyclerView.Adapter<WordAdapter.WordViewHolder>() {
private val filteredWords: List<String>
init {
// Retrieve the list of words from res/values/arrays.xml
val words = context.resources.getStringArray(R.array.words).toList()
filteredWords = words
// Returns items in a collection if the conditional clause is true,
// in this case if an item starts with the given letter,
// ignoring UPPERCASE or lowercase.
.filter { it.startsWith(letterId, ignoreCase = true) }
// Returns a collection that it has shuffled in place
.shuffled()
// Returns the first n items as a [List]
.take(5)
// Returns a sorted version of that [List]
.sorted()
}
class WordViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val button = view.findViewById<Button>(R.id.button_item)
}
override fun getItemCount(): Int = filteredWords.size
/**
* Creates new views with R.layout.item_view as its template
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
val layout = LayoutInflater
.from(parent.context)
.inflate(R.layout.item_view, parent, false)
// Setup custom accessibility delegate to set the text read
layout.accessibilityDelegate = Accessibility
return WordViewHolder(layout)
}
/**
* Replaces the content of an existing view with new data
*/
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
holder.button.setOnClickListener {
val queryUrl: Uri = **Uri.parse("${DetailActivity.SEARCH_PREFIX}${item}")<---Unresolved reference item**
val intent = Intent(Intent.ACTION_VIEW, queryUrl)
**context.startActivity(intent)<---Unresolved reference context**
}
}
val item = filteredWords[position]<--Unresolved reference position
// Needed to call startActivity
val context = holder.view.context <--Unresolved reference holder
// Set the text of the WordViewHolder
**holder.button.text = item** **<---This line is the error Expecting member declaration**
}
// Setup custom accessibility delegate to set the text read with
// an accessibility service
companion object Accessibility : View.AccessibilityDelegate() {
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onInitializeAccessibilityNodeInfo(
host: View?,
info: AccessibilityNodeInfo?
) {
super.onInitializeAccessibilityNodeInfo(host, info)
// With `null` as the second argument to [AccessibilityAction], the
// accessibility service announces "double tap to activate".
// If a custom string is provided,
// it announces "double tap to <custom string>".
val customString = host?.context?.getString(R.string.look_up_word)
val customClick =
AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfo.ACTION_CLICK,
customString
)
info?.addAction(customClick)
}
}
} <------Here is line 118 Expecting a top level declaration
delete the '}' from top of val item declaration

Data disappears when scrolling in recycler view

Good day. So I currently have data in my recycler view. It is for now only static data. I still have to do the code where I import. My problem however is I have a button that changes the background of a text view. This happens in my adapter. And when I scroll through my list the bg color change gets reverted back to what it was before the button click. I have read a lot of similar problems but could not really find one that explains clearly or work for me. From what I read the data gets reset to the static data because it is currently happening in my onBindViewHolder and I think this changes the data on every new data read(scrolling). I read that I should create a link or a listener and then call it. But It does not make sense to me because if a link is called the same amount of times as the code is executed then it will be the same will it not. Maybe having a condition listener but not sure if this is the way to go.
I am somewhat new to android and kotlin. Have been working with it for a month now. I dont know everything I am doing but I got given a deadline. So sadly there was no time to go and learn the basics. Thank you for any and all help. Please let me know if you need any additional code/information
my adapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RowViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.table_list_item, parent, false)
return RowViewHolder(itemView)
}
private fun setHeaderBg(view: View) {
view.setBackgroundResource(R.drawable.table_header_cell_bg)
}
private fun setContentBg(view: View) {
view.setBackgroundResource(R.drawable.table_content_cell_bg)
}
override fun onBindViewHolder(holder: RowViewHolder, position: Int) {
// (TableViewAdapter.DataviewHolder) .bind()
val rowPos = holder.adapterPosition
if (rowPos == 0) {
// Header Cells. Main Headings appear here
holder.itemView.apply {
setHeaderBg(txtWOrder)
setHeaderBg(txtDElNote)
setHeaderBg(txtCompany)
// setHeaderBg(txtAddress)
setHeaderBg(txtWeight)
setHeaderBg(txtbutton1)
setHeaderBg(txtbutton2)
setHeaderBg(txttvdone)
txtWOrder.text = "WOrder"
txtDElNote.text = "DElNote"
txtCompany.text = "Company"
// txtAddress.text = "Address"
txtWeight.text = "Weight"
txtbutton1.text = "Delivered"
txtbutton2.text = "Exception"
txttvdone.text = ""
}
} else {
val modal = Tripsheetlist[rowPos - 1]
holder.itemView.apply {
setContentBg(txtWOrder)
setContentBg(txtDElNote)
setContentBg(txtCompany)
// setContentBg(txtAddress)
setContentBg(txtWeight)
setContentBg(txtbutton1)
setContentBg(txtbutton2)
setContentBg(txttvdone)
txtWOrder.text = modal.WOrder.toString()
txtDElNote.text = modal.DElNote.toString()
txtCompany.text = modal.Company.toString()
// txtAddress.text = modal.Address.toString()
txtWeight.text = modal.Weight.toString()
txtbutton1.text = modal.Button1.toString()
txtbutton2.text = modal.Button2.toString()
txttvdone.text = modal.tvdone.toString()
}
}
holder.apply {
txtbutton1.setOnClickListener {
Log.e("Clicked", "Successful delivery")
txttvdone.setBackgroundResource(R.color.green)
txttvdone.setText("✓")
}
txtbutton2.setOnClickListener {
Log.e("Clicked", "Exception on delivery")
txttvdone.setBackgroundResource(R.color.orange)
txttvdone.setText("x")
}
}
}
class RowViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val txttvdone:TextView = itemView.findViewById<TextView>(R.id.txttvdone)
val txtbutton1:Button = itemView.findViewById<Button>(R.id.txtbutton1)
val txtbutton2:Button = itemView.findViewById<Button>(R.id.txtbutton2)
} class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view){
var txtbutton1 = view.findViewById<Button>(R.id.txtbutton1)
val txtbutton2:Button = itemView.findViewById<Button>(R.id.txtbutton2)
var txttvdone = view.findViewById<TextView>(R.id.txttvdone)
}
I tried (TableViewAdapter.DataviewHolder) .bind() doing this and creating another class as I saw that was done in another thread(Why do values ​disappear after scrolling in Recycler View?) Its a lot like my problem. I just can't seem to implement his solution to make mine work. ( don't understand his solution fully)
//I am also aware that I am using android extensions which will expire at the end of the year. But for now it works and once I have the code up and running I will start to move over to the newer versions of kotlin.
A RecyclerView, as its name implies, will recycle the views when they go off screen. This means that when the view for an item comes into view, it gets recreated and the onBindViewHolder() is called to fill in the details.
Your onClickListener inside your adapter changes the background of one of the subviews for your cell view. However, that cell will be redrawn if it leaves the screen and comes back.
To get around this, your onClickListener should be changing a property on the data item, and your onBindViewHolder should check that property to determine what background color to display for the subview:
enum class DataState {
Unselected,
Success,
Failure
}
data class DataItem(var state: DataState = DataState.Unselected)
class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
var dataItems: List<DataItem> = emptyList()
fun updateData(data: List<DataItem>) {
dataItems = data
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val dataItem = dataItems[position]
holder.txttvdone.apply {
setBackgroundResource(when (dataItem.state) {
DataState.Unselected -> android.R.color.transparent
DataState.Success -> R.color.green
DataState.Failure -> R.color.orange
})
text = when (dataItem.state) {
DataState.Unselected -> ""
DataState.Success -> "✓"
DataState.Failure -> "x"
}
}
holder.apply {
txtbutton1.setOnClickListener {
Log.e("Clicked", "Successful delivery")
dataItem.state = DataState.Success
notifyDataSetChanged()
}
txtbutton2.setOnClickListener {
Log.e("Clicked", "Exception on delivery")
dataItem.state = DataState.Failure
notifyDataSetChanged()
}
}
}
}

ListAdapter submitList not updating

I have this issue with the pagination infinite scroll in RecyclerView, I am adding all new item using .addAll()
movieList.addAll(it.movieList)
adapter.submitList(movieList)
Log.wtf("WTF", movieList.size.toString())
The size keeps increasing whenever we get a success response from API which indicates that the list is indeed being populated but the item in RecyclerView stays the same and submitList() seems to work only at first call.
Here is my DiffUtil class and the Adapter
class DiffUtilMovies : DiffUtil.ItemCallback<MovieItem>() {
// DiffUtil uses this test to help discover if an item was added, removed, or moved.
override fun areItemsTheSame(oldItem: MovieItem, newItem: MovieItem): Boolean {
return oldItem.id == newItem.id
}
// Check whether oldItem and newItem contain the same data; that is, whether they are equal.
// If there are differences between oldItem and newItem, this code tells DiffUtil that the item has been updated.
override fun areContentsTheSame(oldItem: MovieItem, newItem: MovieItem): Boolean {
// Check for now if there is a difference on the price, removing specific fields
// means checking all the data for changes
return oldItem.title == newItem.title
}
}
class MovieAdapter(private val context: Context) : ListAdapter<MovieItem, MovieAdapter.ItemView>(DiffUtilMovies()) {
private var isDetached: Boolean = false
class ItemView(itemView: MovieCardBinding) : RecyclerView.ViewHolder(itemView.root) {
val titleTxt = itemView.titleTxt
val rateTxt = itemView.rateTxt
val rateBar = itemView.rateBar
val imageThumb = itemView.thumbnail
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemView {
return ItemView(
MovieCardBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ItemView, position: Int) {
holder.apply {
val movieItem = getItem(position)
titleTxt.text = movieItem.title
rateTxt.text = movieItem.voteAverage.toString()
val rateAvg = movieItem.voteAverage?.toFloat() ?: run {
0.0f
}
rateBar.rating = rateAvg/2
if (!isDetached)
GlideApp.with(context)
.load(context.getString(R.string.image_link,AppConfig.image_endpoint, movieItem.posterPath))
.thumbnail(GlideApp.with(context).load(R.drawable.loading).centerCrop())
.error(R.drawable.no_image)
.into(imageThumb)
this.itemView.setOnClickListener {
try {
// context.startActivity(Intent(context, AssetInfoActivity::class.java).apply {
// putExtra(context.getString(R.string.assets), movieItem)
// })
}
catch (ignored: Exception){
// The user probably already leave before the activity started
}
}
}
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
isDetached = true
}
}
ListAdapter doesn’t work with mutable lists. This is because if you modify the contents of the List, when it tries to compare the contents of the old list and new list, it’s comparing the same list to itself. There is no old list instance that still holds the old contents, so it cannot detect any differences.
Instead of mutating the original list, you should create a new one, for example
movieList = movieList + it.movieList
adapter.submitList(movieList)
Alternatively, you can use a mutable backing list, but always create a copy when passing it to submitList. You must use a copy even the very first time you pass the List so it is never referring to your mutable List.
movieList.addAll(it.movieList)
adapter.submitList(movieList.toList())

How to update object value in MutableList?

I have a MutableList in my Android project where i'm adding an object called Articolo, then when a new item is added to that list i need to check if one item with same ID exist and if it does i need to update it's quantity.
The issue is that i'm trying to use MutableList.find to find the object with the same ID and when i find it i'm simply add the quantity to existing quantity but instead it remains immutable.
Here is my Articolo.kt
data class Articolo(var barcode: String, var qta: Int) {
constructor() : this ("", 0)
}
And here is my function where i'm adding data to MutableList
private var articoli = mutableListOf<Articolo>()
private fun addBarcode(barcode: String, qta: Int) {
if (barcode.isEmpty()) {
txtBarcode.requestFocus()
return;
}
articoli.find{
it.barcode == barcode
}?.qta?.plus(qta) ?:
articoli.add(Articolo(barcode, qta))
}
So if i add the first object like barcode: 1111, qty: 1 and then another same object instead of having one element array with qty 2 i still have qty 1..
That's because .plus(Int) returns a new value. You're not changing the property.
Instead you should do:
fun addBarcode(barcode: String, qta: Int) {
val existant = articoli.find { it.barcode == barcode }
if (existant != null) existant.qta += qta
else articoli.add(Articolo(barcode, qta))
}
#VaiTon86 has the answer (you're not actually changing the value in the Articolo object) but really, you should probably be using a Map here anyway:
maximum one of each item
lookup by some value (barcode)
that's a map!
There's a few ways you could implement it, here's one:
val articoli = mutableMapOf<String, Articolo>()
private fun addBarcode(barcode: String, qta: Int) {
articoli.getOrPut(barcode) { Articolo(barcode, 0) }
.let { it.qta += qta }
}
So the getOrPut just adds a new zero-quantity Articolo entry if there isn't already one, and then you add qta to what's already there for that entry.

Recyclerview adapter onBindViewHolder payload is not working

I checked all the examples, but they don't work after all. As far as I know, even if payload is 'List', String or Int value can go into.
class RecordListAdapter (val context: Context, val layoutManager: LinearLayoutManager, private val canBeEdited: Boolean)
: RecyclerView.Adapter<RecordListAdapter.RecordViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var records: ArrayList<Record> = arrayListOf()
// Update ALL VIEW holder
override fun onBindViewHolder(holder: RecordViewHolder, position: Int) {
val current = records[position]
holder.autoCompleteTextView.text = SpannableStringBuilder(current.name)
holder.weightPicker.value = current.weight
holder.setPicker.value = current.set
holder.repsPicker.value = current.reps
if(position == itemCount - 1) holder.addBtn.visibility = View.VISIBLE
else holder.addBtn.visibility = View.GONE
if(canBeEdited) {
if(itemCount == 1) {
holder.deleteBtn.visibility = View.GONE
} else {
holder.deleteBtn.visibility = View.VISIBLE
holder.deleteBtn.setOnClickListener {
records.remove(current)
notifyItemRemoved(position)
}
}
} else
holder.deleteBtn.visibility = View.GONE
}
// Update only part of ViewHolder that you are interested in
override fun onBindViewHolder(holder: RecordViewHolder, position: Int, payloads: MutableList<Any>) {
Log.e("payload", "::$payloads")
if(payloads.isNotEmpty()) {
} else
super.onBindViewHolder(holder,position, payloads)
}
private fun addRecordDefault() {
this.records.add(Record("", 28, 5, 10))
notifyItemInserted(itemCount)
notifyItemRangeChanged(itemCount-1, 2, "PAYLOAD_ADD_BTN")
}
override fun getItemCount() = records.size
}
As above code, I set the Log.e to know whether the value is empty or not. The payload Log.e always say it's null.
E/payload: ::[]
Firstly, it seems you are just adding the item and want to change something in it with payloads right away.
Obviously, when you just add a new one, the whole item has to be drawn, thus no payloads needed.
Only then, when it is already drawn and you want to change some elements, you may use payloads with notifyItemChanged (if one item was changed) or notifyItemRangeChanged (if several items were changed).
Secondly, I am not sure regarding the range you use.
The first argument of notifyItemRangeChanged is the start index.
The second one is how many items you want to change.
Thirdly, it's not clear where do you call the addRecordDefault
So make sure you called notifyItemChanged or notifyItemRangeChanged with payloads.

Categories

Resources