Home > Net >  Using LazyListScope Nested Composables
Using LazyListScope Nested Composables

Time:05-01

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) }
}
  • Related