https://kotlinlang.org logo
Title
a

Alexander Maryanovsky

03/26/2022, 1:32 PM
Simplifying a lot, so forgive me if the use-case doesn’t make much sense. I’m trying to implement a
Column
where each row “knows” its index, like so:
@Composable
fun IndexedColumn(
    content: @Composable IndexedColumnScope.() -> Unit
){
    Column {
        content.invoke(IndexedColumnScope())
    }
}

class IndexedColumnScope{

    var index = 0

    @Composable
    fun row(
        rowContent: @Composable IndexedRowScope.() -> Unit
    ){
        val rowIndex = remember { index++ }
        rowContent.invoke(IndexedRowScope(rowIndex))
    }

}

class IndexedRowScope(
    val rowIndex: Int
)

@Composable
fun UseIndexedColumn(){
    IndexedColumn{
        repeat(5) {
            row{
                Text("Row $rowIndex")
            }
        }
    }
}
The problem is with the
var index
— when a row changes,
row
is called again (not recomposition, but regular composition) and
index
increases again. So if I had at the beginning
Row 0
Row 1
Row 2
Row 3
Row 4
and then row 2 changes from
Text
to
BasicTextField
, I’d have
Row 0
Row 1
Row 5  <-- BasicTextField
Row 3
Row 4
:thread-please: 1
Sorry, I guess it’s good manners to put long text in a thread. Didn’t realize that.
s

shikasd

03/26/2022, 3:46 PM
I think you could achieve similar result if you'd just pass index from
repeat
loop as a parameter to the
row
function?
a

Alexander Maryanovsky

03/26/2022, 4:45 PM
Of course, but that's exactly what I'm trying to avoid
The code here is very simplified…
Here’s a complete example reproducing the issue:
class IndexedColumnScope{

    var index = 0

    @Composable
    fun row(
        rowContent: @Composable IndexedRowScope.() -> Unit
    ){
        val rowIndex = remember {
            index++
        }
        rowContent.invoke(IndexedRowScope(rowIndex))
    }

}

class IndexedRowScope(
    val rowIndex: Int
)

@Composable
fun UseIndexedColumn(){
    IndexedColumn{
        repeat(10) {
            var showFirst by remember { mutableStateOf(true) }
            if (showFirst){
                row{
                    Text(
                        text ="Row $rowIndex",
                        modifier = Modifier
                            .clickable {
                                isText = false
                            }
                    )
                }
            }
            else{
                row{
                    Text(
                        text = "Row $rowIndex",
                        modifier = Modifier
                            .background(Color.Gray)
                            .clickable {
                                isText = true
                            }
                    )
                }
            }
        }
    }
}
tbh, I now that I understand the issue, I don’t think it can be solved
When it switches to the “else” row, it’s a completely different call, and I don’t think there’s a way to “preserve” the index.
s

shikasd

03/26/2022, 8:04 PM
Yep, the remembered value for that composable will be wiped You could have if-else inside row call, but I feel like passing index is way more reliable there :)
z

Zach Klippenstein (he/him) [MOD]

03/26/2022, 9:05 PM
Any reason to not just use
LazyColumn
here? It gives you that for free
☝️ 1
a

Alexander Maryanovsky

03/26/2022, 9:26 PM
Well, for one, my actual code does a lot more (it uses the row index internally for things like managing which rows are selected). But where does
LazyColumn
give me the index for free?
m

myanmarking

03/26/2022, 10:14 PM
itemsIndexed?
a

Alexander Maryanovsky

03/27/2022, 4:28 AM
That’s an extension function, it can only give you a local index (into the list/array you give it).