Kotlin list iterator jumps to last element - android

I wrote in code comments primarily whats going on with the code but here is a little more indepth. I created an array of drawable resources (images). I created a button and assigned its function to switch to the next element of the array with a click. ( Once its at the last element I intend to have it go back to element[0] but I haven't gotten that far yet).
I'm pretty new to android development so I hope this isn't a silly question. when I run the app and click the button, it jumps to the very last element. I also noticed it does not even show the first element on the screen ( just blank, then on a button click you see last element). below is the picture of whats happening:
p.s. there used to be more array elements but I deleted them to see if they were the problem.
p.s.s
here is the actual code
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//here I created an array of drawable images to pick from for the hair options
val blankHairStyleList = arrayOf(R.drawable.hairponytailpink , R.drawable.hairponytailblue)
//here I created an iterator which allows me to move through the array.
val blankHairStyleListIterator = blankHairStyleList.iterator()
//created button's click action
findViewById<Button>(R.id.HairButton).setOnClickListener{
//here is the actual iterator function, I feel like something is missing above here.
while(blankHairStyleListIterator.hasNext()){
findViewById<ImageView>(R.id.picture).setBackgroundResource(blankHairStyleListIterator.next())
}
}
photo of whats happeneing

This is a great start! Here's how you can fix it up.
I created a button and assigned its function to switch to the next element of the array with a click.
What you have right now will always cycle to the end because the while loop will keep running as long as #hasNext is true.
while (blankHairStyleListIterator.hasNext()) {
findViewById<ImageView>(R.id.picture).setBackgroundResource(blankHairStyleListIterator.next())
}
If you change the while to an if, the block will only run once!
if (blankHairStyleListIterator.hasNext()) {
findViewById<ImageView>(R.id.picture).setBackgroundResource(blankHairStyleListIterator.next())
}
Once its at the last element I intend to have it go back to element[0] but I haven't gotten that far yet.
Once you have that if statement, you can use the else to create a new iterator (after you make the blankHairStyleListIterator a var instead of a val).
if (blankHairStyleListIterator.hasNext()) {
findViewById<ImageView>(R.id.picture).setBackgroundResource(blankHairStyleListIterator.next())
} else {
findViewById<ImageView>(R.id.picture).setBackgroundResource(blankHairStyleList.first())
blankHairStyleListIterator = blankHairStyleList.iterator()
}
I also noticed it does not even show the first element on the screen (just blank, then on a button click you see last element).
You need to set the element on the ImageView. You can put something like this before the setOnClickListener.
findViewById<ImageView>(R.id.picture).setBackgroundResource(blankHairStyleList.first())
Please let me know if this does the trick for you!

You want something else as per your requirement and your code is doing a different thing.
As per your code, you are setting image background until your iterator has reached last item as below -
findViewById<Button>(R.id.HairButton).setOnClickListener{
while(blankHairStyleListIterator.hasNext()){
findViewById<ImageView>(R.id.picture).setBackgroundResource(blankHairStyleListIterator.next())
}
}
Because you are using while so code will set last item as background by skipping all of the previous items.
You need to make change in your code as per your requirement as below -
findViewById<Button>(R.id.HairButton).setOnClickListener{
if(blankHairStyleListIterator.hasNext()){
findViewById<ImageView>(R.id.picture).setBackgroundResource(blankHairStyleListIterator.next())
}
}

Related

How to use Live Data correctly in my case Android kotlin

HomeFragment
Everything seems to be done correctly, but when you flip the screen, the resulting value from the EditText disappears from the TextView. What to do tell me pls
HomeViewModel
MainActivity
what you are doing wrong is that you start observing the value from the viewmodel only after you click btnSend.
This creates two problems. The first is the obvious one you have found already. You lose the value upon rotating because you have a new View.
The second one is that every time you click on btnSend you are re-observing your LiveData with a new observer object.
You have to move the folowing code inside your OnCreateView after setting the binding value
viewModel.myValue.observe(viewLifecycleOwner) {
binding.txtResult.text = viewModel.myValue.value
}
You can also replace the second part of the assignment with just it as it is suggested in the answer from #JustSightseeing
Try replacing:
viewModel.myValue.observe(viewLifecycleOwner) {
binding.txtResult.text = viewModel.myValue.value
}
with
viewModel.myValue.observe(viewLifecycleOwner) {
binding.txtResult.text = it
}

How to refer to a button that is in the list Kotlin

I am trying to create an app in android studio. I've only recently started to get interested in this and ran into a problem. As planned, for each move in the game, the player presses several buttons, I would like to put the id of these buttons in a separate list, and when necessary, use this list to change the color of all those buttons that are in this list
What i did:
I have the list->
val move_list: MutableList<Any> = mutableListOf()
When the player presses the button, I add its id to move_list
fun for_btn_buba2(view: View){
move_list.add(buba2.id)
In activity_main.xml my button seems like:
<Button
android:id="#+id/buba2"
android:text="Buba 2"
...
android:onClick="for_btn_buba2"/>
And on click of another button i wanted to insert code like this:
move_list[0].setBackgroundColor(Color.parseColor(colorString:"#FFFC9D45"))
move_list[0] means id for button buba2
In python it can be, but it isnt python)
How can I change the color of the button through the list index with the buttons?
Firstly I suggest you indicate the type of what is in the list, which is Int so like
val move_list: MutableList<Int> = mutableListOf()
Then you can probably do this in the onClick of the other button
findViewById<View>(move_list[0]).setBackgroundColor(Color.parseColor("#FFFC9D45"))
or to do it to all
move_list.forEach {
findViewById<View>(it).setBackgroundColor(Color.parseColor("#FFFC9D45"))
}
I also notice you directly refer to buba2 in it the for_btn_buba2. I have the feeling that you are writing this function for every buba. This is unnecessary. You can get the id from the view parameter because that is in fact the same id. So do
fun for_btn_buba(view: View){
move_list.add(view.id)
}
then you can give each buba the same for_btn_buba as android:onClick
Alternatively you don't even work with ids at all and make it a
val move_list: MutableList<View> = mutableListOf()
and then do
fun for_btn_buba(view: View){
move_list.add(view)
}
and then you can actually change the background like you wrote it:
move_list[0].setBackgroundColor(Color.parseColor("#FFFC9D45"))
or
move_list.forEach {
it.setBackgroundColor(Color.parseColor("#FFFC9D45"))
}

Compose: Why does a list initiated with "remember" trigger differently to Snapshot

I've been messing around with Jetpack Compose and currently looking at different ways of creating/managing/updating State.
The full code I'm referencing is on my github
I have made a list a piece of state 3 different ways and noticed differences in behavior. When the first list button is pressed, it causes all 3 buttons to be recomposed. When either of the other 2 lists are clicked though they log that the list has changed size, update their UI but trigger no recompose of the buttons ?
To clarify my question, why is that when I press the button for the firsList I get the following log messages, along with size updates:
Drawing first DO list button
Drawing List button
Drawing second DO list button
Drawing List button
Drawing third DO list button
Drawing List button
But when I press the buttons for the other 2 lists I only get the size update log messages ?
Size of list is now: 2
Size of list is now: 2
var firstList by remember{mutableStateOf(listOf("a"))}
val secondList: SnapshotStateList<String> = remember{ mutableStateListOf("a") }
val thirdList: MutableList<String> = remember{mutableStateListOf("a")}
Row(...) {
println("Drawing first DO list button")
ListButton(list = firstList){
firstList = firstList.plus("b")
}
println("Drawing second DO list button")
ListButton(list = secondList){
secondList.add("b")
}
println("Drawing third DO list button")
ListButton(list = thirdList){
thirdList.add("b")
}
}
When I click the button, it adds to the list and displays a value. I log what is being re-composed to help see what is happening.
#Composable
fun ListButton(modifier: Modifier = Modifier,list: List<String>, add: () -> Unit) {
println("Drawing List button")
Button(...,
onClick = {
add()
println("Size of list is now: ${list.size}")
}) {
Column(...) {
Text(text = "List button !")
Text(text = AllAboutStateUtil.alphabet[list.size-1])
}
}
}
I'd appreciate if someone could point me at the right area to look so I can understand this. Thank you for taking the time.
I'm no expert (Well,), but this clearly related to the mutability of the lists in concern. You see, Kotlin treats mutable and immutable lists differently (the reason why ListOf<T> offers no add/delete methods), which means they fundamentally differ in their functionality.
In your first case, your are using the immutable listOf(), which once created, cannot be modified. So, the plus must technically be creating a new list under the hood.
Now, since you are declaring the immutable list in the scope of the parent Composable, when you call plus on it, a new list is created, triggering recompositions in the entire Composable. This is because, as mentioned earlier, you are reading the variable inside the parent Composable's scope, which makes Compose figure that the entire Composable needs to reflect changes in that list object. Hence, the recompositions.
On the other hand, the type of list you use in the other two approaches is a SnapshotStateList<T>, specifically designed for list operations in Compose. Now, when you call its add, or other methods that alter its contents, a new object is not created, but a recomposition signal is sent out (this is not literal, just a way for you to understand). The way internals of recomposition work, SnapshotStateList<T> is designed to only trigger recompositions when an actual content-altering operation takes place, AND when some Composable is reading it's content. Hence, the only place where it triggered a recomposition was the list button that was reading the list size, for logging purposes.
In short, first approach triggers complete recompositions since it uses an immutable list which is re-created upon modification and hence the entire Composable is notified that something it is reading has changed. On the other hand, the other two approaches use the "correct" type of lists, making them behave as expected, i.e., only the direct readers of their CONTENT are notified, and that too, when the content (elements of the list) actually changes.
Clear?
EDIT:
EXPLANATION/CORRECTION OF BELOW PROPOSED THEORIES:
You didn't mention MutableListDos in your code, but I'm guessing it is the direct parent of the code you provided. So, no, your theory is not entirely correct, as in the immutable list is not being read in the lambda (only), but the moment and the exact scope where you are declaring it, you send the message that this value is being read then and there. Hence, even if you removed the lambda (and modified it from somewhere else somehow), it will still trigger the recompositions. The Row still does have a Composable scope, i.e., it is well able to undergo independent recompositions, but the variable itself is being declared (and hence read) in the parent Composable, outside the scope of the Row, it causes a recomp on the entire parent, not just the Row Composable.
I hope we're clear now.

Add user back to array and put to front

I want to create a regret button, kind of like in the tinder app.
I want this button to look at the last removed user from the array, and put it back into view.
I have tried with this code, which adds to user back to the queue, but not when pressing. The user will be put at index 0, which is next in line. If I switch out the index, the app crashes so I assume another way has to be done.
Furthermore, the button can be pressed multiple times, and the user will appear multiple times in the view. You can see the commented code in the if statement, where I tried to solve this
fabRegret.setOnClickListener {
if (rowItems.size != 0)//&& rowItems[0] != lastDeleted)
rowItems.add(0, lastDeleted!!)
Toast.makeText(getContext(), "User added to queue", Toast.LENGTH_LONG).show()
cardAdapter!!.notifyDataSetChanged() //undo button can be pressed multiple times to add same person again
}
For clarifitcation:
rowItems = ArrayList()
lastDeleted = rowItems.removeAt(0)
First, I would please you to ask questions more precisely. I'm not sure if the following answer can help to solve your problem.
As both commentators already said, there are multiple ways to implement an Undo / History Behaviour.
1. Design Pattern
I recommend you to use the command design pattern, as acarlstein already suggested.
Here is another link I can recommend.
2. Stack (duplicates)
If you want to implement it the quick & easy way, you could use a Stack instead of an ArrayList.
It's a LIFO (Last In - First Out) Storage.
You can implement the Stack (can hold duplicates) as follows:
val stack = stackOf(itemOne, itemTwo, itemThree)
stack.push(itemFour)
val item = stack.pop() // itemFour
But as I understand your question, you don't want to have duplicates in your history.
3. Stack adaption (no duplicates)
Without duplicates, you can adapt the Stack and override it's push() method to avoid duplicates.
Like this:
class UniqueStack<E> : Stack<E>() {
override fun push(item : E) : E {
// when item is already part of the stack, push it to the top
if (contains(item)) remove(item)
return super.push(item)
}
}
An implementation could look like:
val history = UniqueStack<Int>()
history.push(1) // [1]
history.push(1) // [1]
history.push(2) // [1, 2]
history.push(1) // [2, 1]
history.push(1) // [2, 1]
println(history) // prints "[2, 1]"
println(history.pop()) // prints "1"
println(history) // prints "[2]"
I hope I was able to help you.

setListAdapter(adapter_name) to change the list displayed on screen, but still appears information from the previous displayed list

I have an activity that extends ListActivity, a list of "concepts" (let's call this list "C") and an onItemClickListener defined for this list. Whenever I click a "concept", no matter which one, the app must display another list. I have the following code to change the displayed list:
if(position == 0) change_list("adapter1");
else if (position == 1) change_list("adapter2");
else if (position == 2) change_list("adapter3");
else if (position == 3) change_list("adapter4");
else if (position == 4) change_list("adapter5");
Where position is the position of the clicked element in C
The function change_list performs setListAdapter(parameter) depending on the parameter I pass.
If I click the first element of C (the first concept), a list related to the first concept must appear. However, after calling setListAdapter(adapter), the data related to this concept is displayed, and also part of the C's list data.
For example: let's suppose C has these concepts:
A B C D E
and I click "A", which would lead to display a list with the following data: {a1,a2}
That's the final result:
a1 a2 C D E
And then, when I interact with another element on screen or I scroll down the list, the "ghost" data disappears and only the correct data remains on screen, just like this:
a1 a2
To make things worse, when I want to display list C again, nothing strange happens. Everything is displayed correctly.
At any time incorrect data is stored where it doesn't have to. One function my app must allow is to generate a txt file , and the generated txt file contains exactly the data I introduced. No data is corrupted or duplicated. I also tried using notifyDataSetChanged() and other functions, but I didn't solve the problem.
EDIT :
Here goes the xml code used for the main list of the activity:
<ListView
android:id="#android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#FF0000"
android:layout_below="#+id/afegir"/>
And an example of code in which I determine which contents must be displayed on screen:
else if(comprovar_concepte_actiu() == 1){
pnt = mydbhandler.getStoredValues("despeses1");
pnt.moveToFirst();
if(pnt.moveToFirst()){
do{
adapter_mostrar.add(pnt.getString(pnt.getColumnIndex("nom")));
}while(pnt.moveToNext());
}
adapter_mostrar.notifyDataSetChanged();
}
Where comprovar_concepte_actiu() returns and integer that tells which concept has been clicked in the main list C and adapter_mostrar is the single adapter I'm using now, instead of using multiple adapters (which made me use setListAdapter)
At the beginning of the activity, I call this.setListAdapter(adapter_mostrar). That's all I have.
EDIT 2 :
https://www.dropbox.com/s/7twgy043lkxb2x5/conceptes.java?dl=0
Here is a link to my conceptes.java activity. Press CTRL+F once opened and search "this is where I call.. " and you will directly get to the function where the change of list displayed on screen starts
I haven't found a solution yet. Any idea will be totally appreciated
The problem here is that - when you set a new adapter - the old data is still drawn. In other words, there has been no command to "refresh" the listView. However, the new adapter will be commanded to draw its own views. What ultimately occurs is that the old items are still there, the new items are redrawn, but when scrolled away the new adapter won't redraw/recreate the old items.
The solution is to simply refresh the adapter. However, there are two ways to go about this:
Add a new adapter every time and use myListView.invalidateViews(); or something similar [This is probably the easiest solution to implement, although probably not the best in the long run]
Change the dataset of the adapter and use notifyDataSetChanged() [on the adapter]
The latter option is a far better idea. You should use a single adapter and simply change its data over time. Once its dataset is changed, then tell the adapter that such a thing happened so it refreshes. However, you should read more here on all the different thoughts and processes about it, rather than take my opinion on it.
Edit:
There's apparently some very nicely, thought out answers around. Here's another one, that tells you more specifically about the differences between these two:
Is there any difference between ListView.invalidateViews() and Adapter.notifyDataSetChanged()?
Edit2:
With the onClickListener in mind, invalidateViews() will most likely not work, as it'll probably still draw the old views to "finish" the click (ie, draw the highlighting).
Changing the data directly inside a single adapter and using Adapter.notifyDataSetChanged() is your best bet, as it'll know to redraw everything from a single adapter and use only the current data defined by this single adapter.
Best to leave the data specifics (and defining what to draw based off of that data) up to what actually knows the data, rather than a higher up container that knows nothing specific about the actual data.

Categories

Resources