Jetpack Compose: nested LazyColumn / LazyRow
H

6

54

I've read through similar topics but I couldn't find satisfactory result:

My use-case is: to create a comments' list (hundreds of items) with possibility to show replies to each comment (hundreds of items for each item).

Currently it's not possible to do a nested LazyColumn inside another LazyColumn because Compose will throw an exception:

java.lang.IllegalStateException: Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.

The solutions provided by links above (and others that came to my mind) are:

  • Using fixed height for internal LazyColumn - I cannot use it as each item can have different heights (for example: single vs multiline comment).
  • Using normal Columns (not lazy) inside LazyColumn - performance-wise it's inferior to lazy ones, when using Android Studio's Profiler and list of 500 elements, normal Column would use 350MB of RAM in my app comparing to 220-240MB using lazy Composables. So it will not recycle properly.
  • Using FlowColumn from Accompanist - I don't see any performance difference between this one and normal Column so see above.
  • Flatten the list's data source (show both comments and replies as "main" comments and only make UI changes to distinguish between them) - this is what I was currently using but when I was adding more complexity to this feature it prevents some of new feature requests to be implemented.
  • Disable internal LazyColumn's scrolling using newly added in Compose 1.2.0 userScrollEnabled parameter - unfortunately it throws the same error and it's an intended behaviour (see here).
  • Using other ways to block scrolling (also to block it programatically) - same error.
  • Using other LazyColumn's .height() parameters like wrapContentHeight() or using IntrinsicSize.Min - same error.

Any other ideas how to solve this?

Hillie answered 17/5, 2022 at 9:3 Comment(6)
Check out this youtube video about the topic as well as this answer for a basic example - shortly you can place your subcomments in a separate item/items. Also this answer may be useful for building a dynamic items tree.Towill
Thanks @Pylyp Dukhov for your input, it was helpful, the main difference between my implementation and your suggestions was using forEachIndexed with manual control of item and items instead of itemsIndexed that I was using. However, it breaks pagination implemented like here as itemsIndexed has different (user-aware) index value comparing to forEachIndexed one). At the end, it seems I managed to mix both "mindsets" to use itemsIndexed for comments and forEach for replies.Hillie
you should use key parameter of item/items to specify a unique identifier for each item, also itemContent can improve performance if you specify different types depending on cell type (comment/reply)Towill
Sad that this still doesn't have an answer. I have comment trees in my application and am still forced to use Column for the inner branches.Cope
Does this answer your question? How to build a tree using LazyColumn in Jetpack Compose?Cope
This was answered by Phil here: #71667799Cope
M
5

I had a similar use case and I have a solution with a single LazyColumn that works quite well and performant for me, the idea is to treat your data as a large LazyColumn with different types of elements. Because comment replies are now separate list items you have to first flatten your data so that it's a large list or multiple lists. Now for sub-comments you just add some padding in front but otherwise they appear as separate lazy items. I also used a LazyVerticalGrid instead of LazyColumn because I've had to show a grid of gallery pictures at the end, you may not need that, but if you do, you have to use span option everywhere else as shown below.

You'll have something like this:

LazyVerticalGrid(
        modifier = Modifier
            .padding(6.dp),
        columns = GridCells.Fixed(3)
    ) {
        item(span = { GridItemSpan(3) }) {
            ComposableTitle()
        }
        items(
            items = flattenedCommentList,
            key = { it.commentId },
            span = { GridItemSpan(3) }) { comment ->
                ShowCommentComposable(comment) 
                //here subcomments will have extra padding in front
        }
        item(span = { GridItemSpan(3) }) {
            ComposableGalleryTitle()
        }
        items(items = imageGalleryList,
            key = { it.imageId }) { image ->
                ShowImageInsideGrid(image) //images in 3 column grid
    }
}
Millner answered 30/9, 2022 at 10:53 Comment(0)
R
2

In order to prevent problems with nested LazyColumn or LazyRow components within a scrollable screen, where it's not always easy to determine the number of items in advance, you can implement a solution in your ViewModel. By creating a function that calculates the height of the LazyColumn dynamically based on the size of the list, you can ensure that the layout and scrolling behavior work smoothly."

This solution can help you avoid crashes and layout issues when dealing with nested scrollable components like LazyColumn or LazyRow within a scrollable screen.

Calculate LazyColumn Height on run time:

fun getHeightOfLazyColumn(numberOFItemsInlist: Int): Int 

{
        return if (size % numberOfItemsInEachRow == 0) (numberOFItemsInlist/numberOfItemsInEachRow) * heightingleIteminLazyColumn
        else return (((numberOFItemsInlist.toDouble() / 3.0) + 1) *  heighsingleIteminLazyColumn).toInt()
    }

My function:

fun getNumberDepartments(size: Int): Int {
    return if (size % 3 == 0) (size / 3) * 130
    else return (((size.toDouble() / 3.0) + 1) * 130).toInt()

}

After this Call this Function in your UI

LazyColumn(modifier = Modifier.fillMaxSize()) {
        item {
            LazyColumn(modifier = Modifier.height(viewModel.getHeightOfLazyColumn(listOfItems.size))){
                item {
                    //content
                }
            }
        }

    }
Radical answered 8/11, 2023 at 10:45 Comment(0)
P
1

For cases where

  • a flattened LazyColumn is undesirable, (for eg. the inner LazyColumn has a background that spans its items or something),
  • and where that inner list is best represented by a LazyColumn (for eg. because you want item animations out of the box)

a workaround is to specify a fixed height for the inner LazyColumn, if it's possible to know it. Either determine the desired height using a subcompose layout or similar, or if your items have fixes sizes, compute the total height manually.

@Composable
fun MyScreen() {
    LazyColumn {
        item {
            // ...
        }
        item {
            // We want to animate item placement which is only possible out of the box
            // with a LazyColumn. Nested scrollables are not allowed in Jetpack Compose
            // due to single layout pass requirements, however if we have a specified
            // height for the LazyCol it is allowed. So we must compute the expected
            // height ahead of time.
            val cardsHeightSum = CardHeight * cards.size
            val cardsPaddingSum = CardSpacing * (cards.size - 1)
            LazyColumn(
                verticalArrangement = Arrangement.spacedBy(8.dp),
                userScrollEnabled = false,
                modifier = Modifier
                    .background(Color.Blue, RoundedCornerShape(8.dp))
                    .height(cardsHeightSum + cardsPaddingSum)
            ) {
                items(
                    count = cards.size,
                    key = { index -> cards[index].id },
                ) { index ->
                    MyCard(
                        model = cards[index],
                        modifier = Modifier.animateItemPlacement()
                    )
                }
            }
        }
    }
}
Plusch answered 19/7, 2023 at 0:26 Comment(0)
B
0
LazyColumn(modifier = Modifier
                .fillMaxSize()
                .wrapContentSize(unbounded = false),) {
    item {
        val height = 200.dp//Must Calculate your own height
        LazyColumn(userScrollEnabled = false,
        modifier = Modifier.fillMaxWidth().height(height))(
            
        )
    }
    item {
        LazyVerticalGrid(
        columns = GridCells.Fixed(5),
        modifier = Modifier.fillMaxWidth().height(284.dp),
        horizontalArrangement = Arrangement.SpaceAround,
        userScrollEnabled = false) 
    }
    item{
    //other view or Vertical
    }

}

I used a LazyColumn nested with a LazyVerticalGrid, it looks like a LazyColum can only have one vertical scroll setting, the rest can't, you need to turn off scrolling and calculate its height

Bicentennial answered 3/7, 2024 at 6:26 Comment(0)
S
-2

you can use a customView thats implements a "RecyclerView" class and then put it on your compose fragment. I'll show you my solution, works pretty well for me.

compose view

customViewRecyclerView

enter image description here

Of course you haves to create the adapter and the view for each item of the list, but I think you can do it without my help, good luck! :D

Sherl answered 1/4, 2023 at 12:8 Comment(2)
Please add code and data as text (using code formatting), not images. Images: A) don't allow us to copy-&-paste the code/errors/data for testing; B) don't permit searching based on the code/error/data contents; and many more reasons. Images should only be used, in addition to text in code format, if having the image adds something significant that is not conveyed by just the text code/error/data.Accuse
It is really a bad practice to use the androidview inside the compose in such case that there is better solution like using for/foreachFriendly
S
-9
LazyColumn {
  
  item {
    LazyRow {
      // your code
    }
  }

  items(yourList) {
    // your code  
  }

}
Shrewish answered 13/10, 2022 at 19:6 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.