What's the best way to implement a comment Composa...
# compose
a
What's the best way to implement a comment Composable and nested replies that follow it? I tried using nested
LazyColumn
but it's prohibited, so now I'm using a
Column
and loop thru items but that's not the most optimal solution. Any ideas?
b
Assuming a basic design where the replies just need to be indented and there are no separators, or background colors or anything, I might do something like this, using padding on the replies for indentation: First, build basic composables to show a comment, and to show a reply:
Copy code
/** The amount of indentation space that replies should use **/
private val REPLY_INDENTING = 16.dp

@Composable
fun Comment(text: String, modifier: Modifier = Modifier) {
   // Include whatever you need here to render a comment. 
   // For this basic example, just show the comment text.
   Text(text, modifier = modifier)
} 

@Composable
fun Reply(replyText: String, depth: Int = 1, modifier: Modifier = Modifier) {
    // we want replies to be "indented".
    // This basic setup will just use padding to achieve the indenting.
    // We'll multiply the reply depth by the indenting amount to get the
    // proper amount of indentation for this item.

    val indentation = REPLY_INDENTING * depth

    Text(replyText, modifier = modifier.padding(start = indentation))
}
Then, I’d build a function scoped to a LazyList that will render a list of replies with indenting. It’ll be recursive so it can show replies to replies. This function does NOT need to be a Composable
Copy code
fun LazyListScope.Replies(
    replies: List<Reply>, 
    depth: Int = 1, // Used as a multiplier to calculate indenting 
    modifier: Modifier = modifier) {

    replies.forEach { reply ->
        item {
            Reply(
                reply.text,
                depth = depth,
                modifier = modifier)
        }

        Replies(
            replies = reply.replies, 
            depth = depth + 1,
            modifier = modifier)
    }
}
Finally, I can build my main comments (lazy) column:
Copy code
@Composable
fun CommentList(comments: List<Comment>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        comments.forEach { comment ->
            item {
                Comment(comment.text, modifier = Modifier.fillMaxWidth())
            }
            
            Replies(
                replies = comment.replies, 
                depth = 1, 
                modifier = Modifier.fillMaxWidth())
        }
    }
}
I’m no expert, and YMMV ….
a
Thanks, but one problem is that you can't call a Composable function inside
LazyColumn
.
b
Oh yeah. You don’t need the
@Composable
annotation on the
LazyListScope.Replies
function
it doesn’t need to be a composable
The `LazyListScope`’s
item
method takes a lambda, and THAT is a
@Composable
where things get rendered
a
Okay, let me try.
Is there a reason you went with a for-loop instead of using the
items
extension function?
b
Not really. To me it makes this example a little bit more clear That in terms of the List (column), the comment is one item in the list, and then the replies are another item in the list. But I think you’d get the same result just using
items
instead of a
forEach -> item
There’s also some things to consider around an Item having a key (which is useful for animations and things) … if you use the
items
method, then each comment AND it’s replies are an “item” and will be treated as a unit … With the approach i took, the comment is an “item”, and each reply is another “item”. Each is independent and stands on it’s own. So that will affect behavior a little bit. I don’t know that either one is “more correct”. It just depends on how you want it to behave.
You also might be able to write your own modifier that would render a reply and move it over to create the indentation that you want. Or you might be able to achieve that by using the layout modifier to build a custom layout for the reply … I’m not super knowledgeable on those though, so I’d get advice from someone with more expertise if I was going to try that.
a
It's only working only while using for-loop, and I can't figure out why is that. I'm trying to use the items function like this:
Copy code
items(comments) { comment ->
    CommentItem(comment)
    this@comment.replies(comment.replies)
}
fun LazyListScope.replies(replies: List<Comment>) {
    items(replies) { reply ->
        CommentItem(reply)
        this@replies.replies(reply.replies)
    }
}
Do you have any idea why?
Anyway, thank you so much. I'll go with your solution 🙂
@Bradleycorn Do you have an idea on how to one side border (line) to replies using this approach? Thanks in advance.
b
Are you using it as a divider/separator between items? If so there is a
Divider
composable that does just that.
Divider
is a vertical separator (it draws a horizontal line). If you want a vertical line, I’m not sure of anything “out of the box” that does that. But, you could write your own
VerticalDivider
pretty easily. If you look at the code for
Divider
it’s just a
Box
with some modifiers to set it’s size and background color. You could do the same thing, just invert the sizing modifiers (instead of
.fillMaxWidth.height(..)
, do
fillMaxHeight().width(..)
)
a
I'm aware of the
Divider
composable. However I'm asking about how to add lines to replies so they look like a thread, similar to padding for items.
b
Sounds like you want a vertical line next to the reply then. As I mentioned, I’d look at the source for
Divider
and write my own
VerticalLine
composable to do it. Then make each reply a
Row
with a
VerticalLine
and the reply content.
a
Thanks, I don't know why I haven't thought of that. I spent a whole day trying to write recursive Modifier lol to achieve this. Thank you.