Simplifying a lot, so forgive me if the use-case d...
# compose
a
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:
Copy code
@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
Copy code
Row 0
Row 1
Row 2
Row 3
Row 4
and then row 2 changes from
Text
to
BasicTextField
, I’d have
Copy code
Row 0
Row 1
Row 5  <-- BasicTextField
Row 3
Row 4
🧵 1
Sorry, I guess it’s good manners to put long text in a thread. Didn’t realize that.
s
I think you could achieve similar result if you'd just pass index from
repeat
loop as a parameter to the
row
function?
a
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:
Copy code
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
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
Any reason to not just use
LazyColumn
here? It gives you that for free
☝️ 1
a
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
itemsIndexed?
a
That’s an extension function, it can only give you a local index (into the list/array you give it).
a
Still battling with this 2 years later :–)
m
And what other index you need apart from local index?
a
The index global to my "column".
s
I'd still consider passing it from outside Assuming you are using this index for /something/, it makes more sense to have it at a part of item parameters rather than configured inside the column
a
That's what I'm doing, Andrei, but it's not very robust.
m
Column -> fasforeachindexed -> key(..) composable
???
a
I'm forced to do this:
Copy code
IndexedColumn {
    var index = Section1(firstIndex = 0)
    index += Section2(firstIndex = index)
}
but this breaks if I need to, for example, wrap
Section2
in
CompositionLocalProvider
m
I dont understand what you want. But yes, your approach wont work
Because recompositions and skippable
a
I'm using the index together with a
SelectionModel
to know inside the row whether it's currently the "selected" row and draw its background differently (among other things)
m
Just give the list item class an index. And then have a 'selectedindex' variable
Then do if.else to check for selection changes
a
There is no "list item"
m
Explain why a regular foreachindexed wont work please
a
@myanmarking Because
Copy code
IndexedColumn {
    forEachIndexed(10) {
        // 0..9
    }
    forEachIndexed(10) {
        // 0..9 but should be 10..19
    }
}
m
Why repeat the loop?
a
Because why not
s
The problem you are facing is mostly caused by the fact that you are trying to calculate indices through recompositions What you want to do instead is model it as a list of items first. That'll be your centralized state that you can later convert to composition.
m
Ok. 1s
s
Also LazyColumn kinda does the same thing, it takes a DSL with composable blocks and then creates a list of items to compose, you might want to look into the way it does that.
a
@shikasd I understand, but that would be really inconvenient. I'm trying to display 10 sections in this column, and each section is completely different.
m
Just use two columbs then. No double loop
You know the size right?xd
a
I did look. In LazyColumn the
content
lambda is not composable.
and that's also a deal breaker for me
m
The way you want to solve this is hard :p because its kinda wrong approach
s
Why do you want to have content lambda as composable?
a
because each section collects as state a different
StateFlow
(and remembers some things) to find out what rows it has. Each section is a whole UI...
s
Tbf, your model can be just a list of composable lambdas, you don't need to model it as a data class
Then you have a list of items + selection index which you can combine into a column
a
screenshot.png
(the table in the middle)
Each section behaves differently
has its own data source etc.
m
Just use nested columns then
a
Then I'm back to square 1 w.r.t. selection
m
Im not at the computet atm. If tomorrow morning you still havent find ansolution, pm me. Ill help you figure out a way
Need 2 go
a
Ideally, the code should look like this:
Copy code
ColumnWithSelection(
    selectionModel = rememberSingleSelectionModel()
) {
    DroneSection(dataSource)
    ImplantSection(dataSource)
}

@Composable
fun ColumnWithSelectionScope.DroneSection(dataSource: DataSource) {
    val drones = dataSource.drones.collectAsState()
    for (drone in drones) {
        DroneRow(drone)
    }
}

@Composable
fun ColumnWithSelectionScope.ImplantSection(dataSource: DataSource) {
    val implants = dataSource.implants.collectAsState()
    for (implant in implants) {
        ImplantRow(implant)
    }
}

@Composable
fun ColumnWithSelectionScope.DroneRow(drone: Drone) {
    row { ... }
}

@Composable
fun ColumnWithSelectionScope.ImplantRow(implant: Implant) {
    row { ... }
}
right now I'm forced to pass the index into the sections and down to the `row`s, and have each section return the number of rows it added so I can add it to the index outside.