I have a composable that needs to iterate over and render a Map<String, List<String>>
. They key is a "header" and the list sub-items.
I would like to write my code as follows:
@Composable
fun renderFullMap(groupedItems: Map<String, List<String>>) {
LazyColumn {
groupedItems.forEach { (header, things) ->
renderSection(header, things)
}
}
}
@Composable
fun renderSection(header: String, things: List<String>) {
stickyHeader { Text(header) }
items(things) { thing -> Text(thing) }
}
(In practice, the actual data and renderSection
are both more complicated, but they boil down to this basic idea.)
This doesn't work. It tells me:
@Composable invocations can only happen from the context of a @Composable function
If I use items
to iterate over my map keys, I get a different error:
@Composable
fun renderFullMap(groupedItems: Map<String, List<String>>) {
LazyColumn {
items(groupedItems.keys.toList()) { header ->
renderSection(header, groupedItems[header])
}
}
}
With this version, stickyHeader
and items
are undefined inside of renderSection
.
One last thing I tried - prefixing renderSection
with LazyListScope.
:
@Composable
fun LazyListScope.renderSection(header: String, things: List<String>) {
stickyHeader { Text(header) }
items(things) { thing -> Text(thing) }
}
This was just a stab in the dark. If I call that in a forEach
it tells me it gives me the first error about Composables only callable from other Composables. If I call it from within items()
:
'fun LazyListScope.renderSection(header: String, things: List): Unit' can't be called in this context by implicit receiver. Use the explicit one if necessary
I won't pretend to know what that means.
How do I make this work? I suspect I somehow need to extends LazyListScope down into renderSection
but am unclear as to how.
Edit: I sort of made it work, but my list items are multiplying as I scroll around. This code compiles:
@Composable
fun renderFullMap(groupedItems: Map<String, List<String>>) {
LazyColumn {
val scope = this
items(groupedItems.keys.toList()) { header ->
scope.renderSection(header, groupedItems[header])
}
}
}
@Composable
fun LazyListScope.renderSection(header: String, things: List<String>) {
stickyHeader { Text(header) }
items(things) { thing -> Text(thing) }
}
I don't know why the contents of the list is out of whack, however.
CodePudding user response:
Your first approach is close to the correct one, you need to remove @Composable
from renderSection
and add LazyListScope
context:
@Composable
fun renderFullMap(groupedItems: Map<String, List<String>>) {
LazyColumn {
groupedItems.forEach { (header, things) ->
renderSection(header, things)
}
}
}
fun LazyListScope.renderSection(header: String, things: List<String>) {
stickyHeader { Text(header) }
items(things) { thing -> Text(thing) }
}