'How to build a tree using LazyColumn in Jetpack Compose?

In my jetpack-compose app, I'm building a comment tree, where the top level, and the leaves, are lists, that would be best to use LazyColumn.

This is of the form:

List<CommentNode>
...
CommentNode: {
  content: String
  children: List<CommentNode>
}
@Composable
fun Nodes(nodes: List<CommentNode>) {
  LazyColumn {
    items(nodes) { node -> 
      Node(node)
    }
  }
}

@Composable
fun Node(node: CommentNode) {
  LazyColumn {
    item {
      Text(node.content)
    }
    item {
      Nodes(node.children)
    }
  }
}

On the top level, LazyColumn works, but it seems I have to use Column for the leaves, otherwise I get an unexplained crash:

03-29 14:36:38.792  1658  6241 W ActivityTaskManager:   Force finishing activity com.jerboa/.MainActivity 
03-29 14:36:38.902  1658  3033 I WindowManager: WIN DEATH: Window{d3b902b u0 com.jerboa/com.jerboa.MainActivity}                      
03-29 14:36:38.902  1658  3033 W InputManager-JNI: Input channel object 'd3b902b com.jerboa/com.jerboa.MainActivity (client)' was disposed without first being removed with the input manager!

Has anyone had any luck building a variable length tree in jetpack compose?



Solution 1:[1]

I don't think you really need to place one LazyColumn into an other one - each of them gonna have it's own scroll logic.

Instead you can place item for each node recursively. To do this, declare your function on LazyListScope. These are no longer views, since the views will be inside item. And I think the lowercase naming would be correct here.

@Composable
fun View(nodes: List<CommentNode>) {
    val expandedItems = remember { mutableStateListOf<CommentNode>() }
    LazyColumn {
        nodes(
            nodes,
            isExpanded = {
                expandedItems.contains(it)
            },
            toggleExpanded = {
                if (expandedItems.contains(it)) {
                    expandedItems.remove(it)
                } else {
                    expandedItems.add(it)
                }
            },
        )
    }
}

fun LazyListScope.nodes(
    nodes: List<CommentNode>,
    isExpanded: (CommentNode) -> Boolean,
    toggleExpanded: (CommentNode) -> Unit,
) {
    nodes.forEach { node ->
        node(
            node,
            isExpanded = isExpanded,
            toggleExpanded = toggleExpanded,
        )
    }
}

fun LazyListScope.node(
    node: CommentNode,
    isExpanded: (CommentNode) -> Boolean,
    toggleExpanded: (CommentNode) -> Unit,
) {
    item {
        Text(
            node.content,
            Modifier.clickable {
                toggleExpanded(node)
            }
        )
    }
    if (isExpanded(node)) {
        nodes(
            node.children,
            isExpanded = isExpanded,
            toggleExpanded = toggleExpanded,
        )
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1