How to refer to outer this on forEach in Kotlin - android

I have the following case
someThing.forEach{
someWidget.setOnClickListener{
//it is an View
//I need foreach it of someObject
}
}
I read this answer but it does not work
kotlin how to refer outer-scope this in multi-layer apply functions

The problem is that you are not dealing with this here.
forEach has a parameter and for simplicity you can leave it away and just use it instead. Not using it is the same as using _ -> instead... you just discard it.
So your example written with named lambda parameters instead:
someThing.forEach{ some -> // 'it' was available here too, but will not be accessible from within the next setOnClickListener...
someWidget.setOnClickListener{
// some contains one of the someThings now and 'it' is still your View
}
}

You can name the variable in the forEach.
things.forEach { thing ->
someWidget.setOnClickListener {
thing.doSomething()
}
}

I think you mean something like this:
someThing.forEach{ x->
someWidget.setOnClickListener{
//use x
//I need foreach it of someObject
}
}
just use another name like x, you don't have to use it.
Here is an example:
val a = mutableListOf<Int>(1, 3)
val b = mutableListOf<Int>(2, 4)
a.forEach { x ->
b.forEach {
println("" + x + " " + it)
}
}
here x is each item from list a
and it is each item from list b

Related

How do you call a variable created earlier in a when in kotlin?

I'm trying to list the first 100 of a shuffled list. I'm telling it to shuffle if the list is at 0 and then increment. I then am trying to call that list in another section of the when but it's not working. How can I accomplish this?
when (countF) {
0 -> {
//shuffle at 0
val randomChaos = chaosList.asSequence().shuffled().take(chaosList.count()).toList()
cResult.text = randomChaos.elementAt(countF) + countF + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
}
1-99 -> {
//show 1-99
cResult.text = randomChaos.elementAt(countF) + countF + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
}
100 -> countF = 0
You would need to create the val randomChaos before the when enclosure for it to be available in the scope of multiple branches of the when statement.
That said, the way you're getting a random element is very convoluted. take(chaosList.count()) is completely redundant. And since you don't use multiple sequence operators, creating a sequence is also redundant. Finally, you are only pulling a single item from the random list, so it's unnecessary to create a shuffled list in the first place. Using elementAt() on a shuffled list is no different than picking any element out of that shuffled list, or simply picking a random item out of a list that isn't shuffled at all.
Also, the first two branches of your when statement currently would produce exactly the same results so they can be merged.
Based on what you described, I'm guessing you had this when statement inside a loop that tries to run it 100 times so you can list all the items. For that to work, you would need to shuffle the list one time outside the loop, and then you could iterate its elements in the loop.
However, there are functions that can make it easier to do what you're suggesting. Here's an example:
val randomChaos = chaosList.shuffled()
cResult.text = randomChaos.asSequence()
.take(100)
.withIndex()
.joinToString("\n") { (i, value) ->
"$value-$i"
}
In this case, using a Sequence helps avoid creating an intermediate list to hold the first 100 values.
var randomChaos = chaosList.shuffled()
fun cShuf() { randomChaos = chaosList.shuffled() }
cRoll.setOnClickListener() {
cResult.movementMethod = ScrollingMovementMethod()
if (countF < 1) { cShuf() }
cResult.text = randomChaos.elementAt(countF) + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
if (countF > 100) countF = 0
}
I have figured out how to use a function to generate a new shuffe of the list once I've hit > 100 shown.
My issue with making it a function was I was trying to use val variable in the function but the variable already existed so I didn't need to use val, just the name of the variable.

Modifying a SnapshotStateList throws ConcurrentModificationException

The documentation of SnapshotStateList states that it is similar to a regular mutable list. I have a use case where I need to modify all the elements in the list (set case). This does not change the size of the list, but I'm running into ConcurrentModificationException.
I have created a very simplified version of my usecase here. The following kotlin list works fine:
val myList2 = mutableListOf("a", "b", "c")
myList2.forEachIndexed { index, _ ->
// Modify item at index
myList2[index] = "x"
}
But I get a concurrent modification exception here:
val myList = mutableStateListOf("a", "b", "c")
myList.forEachIndexed { index, _ ->
// Modify item at index but I get an exception
myList[index] = "x"
}
How can I modify all elements of mutableStateList() in place without getting the concurrent modification exception?
Edit:
I can create a copy of the mutableStateList to iterate over which works fine but since I'm not changing the size of the list, is it possible to do it in place?
Some possible workarounds are to use replaceAll to transform the list in-place (as long as you don't need the index), or just use an old-fashioned loop over indices if you do
val listA = mutableListOf("A","B","C")
// this works
listA.forEachIndexed { i, s ->
listA[i] = s.lowercase()
}
val listB = mutableStateListOf("A","B","C")
// this fails - as you noted
listB.forEachIndexed { i, s ->
listB[i] = s.lowercase()
}
// this works, as long as you don't need the index
listB.replaceAll { s -> s.lowercase() }
// this also works, and lets you have the index
for(i in listB.indices) {
listB[i] = listB[i].lowercase()
}

how to create dynamic setImageResource

How can I replace something like:
when (content[position].ImageSrc) {
1 -> holder.imageView.setImageResource(R.drawable.image_1)
2 -> holder.imageView.setImageResource(R.drawable.image_2)
3 -> holder.imageView.setImageResource(R.drawable.image_3)
else -> {
holder.imageView.setImageResource(R.drawable.image_x)
}
}
more elegant so something like:
var a = "image_"
var b = content[position].ImageSrc
var c = a + b
holder.imageView.setImageResource(R.drawable.c)
The first code is working but way to laborious, the second one isn't.
Thx:)
Make a Repository that can resolve the asset for you...
in your UI you'd do:
holder.imageView.setImageResource(
myResourceRepo.getImageResourceFor(content[position].ImageSrc)
)
Now what you do in there, it really depends, if you really have 100 resources, you have to find a way to MAP them together, either by creating a mapOf(Int, "img source") (whatever that is), where Int is the R.drawable.xxx and img source is whatever is in content[...].imageSrc (a String?)
This is how you map/associate them together.
If you want it to be more dynamic, then store your images in "raw assets" and using the AssetManager, load the image and convert it into a Drawable at runtime. This way you get to "construct" the asset name like you did
var filename = "some/path/" + content[...].imageSrc
val file = assetManager.open(filename)...
val drawable = makeDrawableFrom(file)
return drawable
Your viewHolder will then use it as
val drawable = repo.getDrawableFrom(content[x].imageSrc)
holder.imageView.setImageResource(drawable)
(NOTE: most of these are "pseudo-code", you have to implement most of it, but it's all possible and quite simple).
If you REALLY want to try to load a resource dynamically... it used to be possible with something like
val nameOfResource = "a" + "b" + "c" //construct your "dynamic" name
val drawable = context.resources.getIdentifier(nameOfResource,
"drawable", context.getPackageName())
(not sure if this still works)
I doubt your "second method" exist instead, you can reformat your first one as follows,
holder.imageView.setImageResource(
when (content[position].ImageSrc) {
1 -> R.drawable.image_1
2 -> R.drawable.image_2
3 -> R.drawable.image_3
else ->R.drawable.image_else
}
)
val imageId = resources.getIdentifier("com.packagename.app:drawable/image_${content[position].ImageSrc}", null, null);
holder.imageView.setImageResource(imageId)

How to detect line breaks in TextView

In my Android App I've created 8 TextViews stacked on top of each other. Now I want to load in some plain text into those TextView-Lines. At the moment my Strings have a ";" as delimiter to indicate a line break, however it would be much more convenient if I would detect a linebreak automatically instead of using the hardcoded semicolon approach.
This is my String at the moment:
myString = "" +
"This seems to be some sort of spaceship,;" +
"the designs on the walls appear to be of;" +
"earth origin. It looks very clean here.;"
And in my other class I load in this string into the 8 TextViews, which I've loaded into an ArrayList, using the ";" as a delimiter.
public fun fillLines(myString: String) {
// How To Make Line Breaks Automatic??
for(i: Int in str until myString.split(";").size) {
if(i > textViewArray.size - 1) {
break
}
textViewArray[i].text = myString.split(";")[i]
textViewArray[i].alpha = 1.0f
}
}
Is there any way I can get the same result as shown above but without hardcoding the delimiter as ";" but instead somehow automatically detect the line break which would occur inside the TextView and then use this as a delimiter to advance through all 8 TextView "Lines".
The reason I need 8 TextViews Stacked On top of each other as individual "text lines" is because of an animation technique I want to use.
Line-breaking gets fairly complicated, so my recommendation would be that you allow a TextView to perform the measuring and layout to determine the line breaks. You could have an invisible TextView with the same style as your other views, and attach it to the layout so that it has the same width as your individual TextView instances. From there, add a layout change listener, and you can then retrieve the individual lines from the TextView Layout:
myTextView.text = // your text string here
myTextView.addOnLayoutChangeListener { view, _, _, _, _, _, _, _, _ ->
(view as? TextView)?.layout?.let { layout ->
// Here you'll have the individual broken lines:
val lines = (0 until layout.lineCount).map {
layout.text.subSequence(layout.getLineStart(it), layout.getLineVisibleEnd(it)
}
}
}
That said, this comes with the caveat that you'll lose out on hyphenation provided by the TextView, so you may wish to disable hyphenation entirely in your case.
You could fill text view with html. Below example.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
tvDocument.setText(Html.fromHtml(bodyData,Html.FROM_HTML_MODE_LEGACY));
} else {
tvDocument.setText(Html.fromHtml(bodyData));
}
If your delimiter ; it is possible call method replaceAll(";", "<br>");
Ok I got it working now:
First you must add these properties for the textviews:
android:singleLine="true"
android:ellipsize="none"
Then you can do this:
public fun fillStorylines() {
val linecap = 46
var finalLine: String
var restChars = ""
val index = 9999
val text1: String = "" +
"This seems to be some sort of spaceship, " +
"the designs on the walls appear to be of " +
"earth origin. It looks very clean here. "
for(j: Int in 0..index) {
try {
finalLine = ""
val lines: List<String> = (restChars + text1.chunked(linecap)[j]).split(" ")
for (i: Int in 0 until lines.size - 1) {
finalLine += lines[i] + " "
}
textViewArray[j].text = finalLine
textViewArray[j].alpha = 1.0f
restChars = lines[lines.size - 1]
} catch (ex: Exception) {
break
}
}
}
If anyone knows a more elegant way to solve this please go ahead, your feedback is appreciated :)

Is Android java getResource.getIdentfier exist in IOS Swift?

My application has so many data. So There are so many textfield. Therefore I want to manage textView in iOS swift in the same way.
area1Layer = new TextView[25];
for(int k = 0; k < layer1; k++){// 층수 SET
area1Layer[k] = (TextView)findViewById(getResources().getIdentifier("layer"+(k+1),"id","kr.soen.areacard"));
area1Layer[k].setText(Integer.toString(k + 1) + "0" +ho1);
}
I assume that you want to identify the respective UITextField/UITextView seperately. This can be done by assigning different tags to the respective textfields/textviews.
let textField1: UITextField = UITextField()
textField1.tag = 1
let textField2: UITextField = UITextField()
textField2.tag = 2
and in the UITextFieldDelegate method,
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField.tag == 1 {
//Type your code here
}
if textField.tag == 2 {
//Type your code here
}
}
Also you could assign outlets to each textfield/textview, and use the outlets to check.
#IBOutlet weak var textField1: UITextField!
#IBOutlet weak var textField2: UITextField!
and in the UITextFieldDelegate method,
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField == textField1 {
//Type your code here
}
if textField == textField2 {
//Type your code here
}
}
The same can be done in the case of UITextView as well.
I believe that you want to identify each UITextField or UITextView separately and accordingly manipulate them. You can do it in the following way:
let textView1: UITextView = UITextView()
textView1.tag = 1
let textView2: UITextView = UITextView()
textView2.tag = 2
self.view.addSubview(textView1)
self.view.addSubview(textView2)
For identifying between different view objects, you can use .tag property in iOS by setting .tag in the above way.
To get different UITextView added to the self.view as a subView in swift, you can do the following:
if let textViewObject: AnyObject = self.view.viewWithTag(2) {
// first check is to identify if there is a given view with the tag
if let textView: UITextView = textViewObject as? UITextView {
}
}
The main difference is iOS uses an number based tag while Android uses a text key. You can accomplish the same thing with both. In iOS store all your labels in a collection and modify per tag. The collection type and syntax specifics for setting the tag depend on the way you wright your views.
After setting the tag via interface builder or view.tag = myTag; you can do something like:
for (int x=0; x<strings.count; x++){
UITextView *view = [self.view viewWithTag:x];
view.text = strings[x];
}

Categories

Resources