2

I want to achieve the following layout enter image description here

All the headers like Claim requested credentials, Claim received credentials, Pending requests etc are dynamic and will be coming from backend and the items for each header like Module 1:... etc are dynamic as well. Also I need the card to encapsulate both Header and the Body item.

Here is what I have tried

LazyColumn(
        modifier = Modifier
            .offset(x = 0.dp, y = 110.dp)
            .fillMaxSize()
            .background(
                shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
                color = MaterialTheme.colors.primaryVariant
            )
            .padding(12.dp)
    ) {
        itemsIndexed(
            listOf(
                "Claim requested credentials",
                "Claim received credentials",
                "Pending Requests"
            )
        ) { index, item ->
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .statusBarsPadding()
                    .background(
                        color = MaterialTheme.colors.primaryVariant,
                        shape = RoundedCornerShape(10.dp)
                    )
                ,elevation = 20.dp,
                contentColor = MaterialTheme.colors.primaryVariant,
                backgroundColor = MaterialTheme.colors.primaryVariant
            ) {
                Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 12.dp)) {
                    ManageCredentialHeader(text = item, vcsNo = 2)
                    LazyColumn(){
                        itemsIndexed(listOf(1,2,3)){ index, item ->
                            ManageCredentialItem()
                        }
                    }
                }
            }
        }
    }

But I get an error saying 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.

How to achieve this kind of dynamic layout with LazyColumn in compose. In react-native this was easily achieved using SectionList.

BraveEvidence
  • 53
  • 11
  • 45
  • 119

3 Answers3

4

Looks like you need a single dynamic LazyColumn here.

You don't have to only use items at the topmost level - if you check out its source code, you'll see that it's just adds single item in a loop.

You can build your lazy content by adding multiple items inside an other loop, something like this:

LazyColumn {
    listOf(
        "Claim requested credentials",
        "Claim received credentials",
        "Pending Requests"
    ).forEach { header ->
        item(
            contentType = "header"
        ) {
            Text(header)
        }
        items(
            count = 10,
            contentType = "body",
        ) { bodyIndex ->
            Text(bodyIndex.toString())
        }
    }
}
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
2

In Jetpack Compose when you create layouts like LazyColumn or any other layout you measure your Composables as Measurables with the limits coming from Constraints as min/max width/height. Modifier.scroll or LazyList returns max as Constraints.Infinity which is not permitted for child LazyColumn. That's why you get that exception.

In this answer i explained which modifier returns which Constraints. You can check out Constraints section.

Since you don't have a height for the Column add a size modifier that changes maxHeight of inner LazyColumn from Constraints.Infinity to something finite such as

   Column(
                    modifier = Modifier
                        .padding(horizontal = 8.dp, vertical = 12.dp)
                        .heightIn(max = 500.dp) {


}

max height is not the fixed value set, it's the biggest height that child LazyColumns can get. You can change it as required. Your items can grow in height up to this.

Implementation

@Composable
private fun LazyListSample() {
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .background(
                shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
                color = Color(0xffE3F2FD)
            )
            .padding(12.dp),
        contentPadding = PaddingValues(vertical = 10.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {

        itemsIndexed(
            listOf(
                "Claim requested credentials",
                "Claim received credentials",
                "Pending Requests"
            )
        ) { index, item ->
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .statusBarsPadding(),
                elevation = 20.dp,
                shape = RoundedCornerShape(10.dp),

                ) {
                Column(
                    modifier = Modifier
                        .padding(horizontal = 8.dp, vertical = 12.dp)
                        .heightIn(max = 500.dp)
                ) {
                    ManageCredentialHeader(text = item, vcsNo = 2)
                    LazyColumn(
                        verticalArrangement = Arrangement.spacedBy(8.dp)
                    ) {
                        itemsIndexed(listOf(1, 2, 3)) { index, item ->
                            ManageCredentialItem()
                        }
                    }
                }
            }
        }
    }
}

@Composable
private fun ManageCredentialHeader(text: String, vcsNo: Int) {
    Row(modifier = Modifier.fillMaxWidth()) {
        Text(text)
        Spacer(modifier = Modifier.width(4.dp))
        Box( modifier = Modifier
            .size(24.dp)
            .background(Color.Blue,CircleShape)
            .padding(2.dp),
            contentAlignment = Alignment.Center
        ) {
            Text(
                vcsNo.toString(),
                color = Color.White
            )
        }

        Spacer(modifier = Modifier.weight(1f))

    }
}

@Composable
private fun ManageCredentialItem() {
    Row(modifier = Modifier.padding(top = 16.dp)) {
        Column() {
            Text("Module 1 some text")
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text("Subtitle")
            }
        }
        Spacer(modifier = Modifier.weight(1f))
        Text(
            "VIEW",
            modifier = Modifier
                .background(Color(0xff283593), RoundedCornerShape(8.dp))
                .padding(6.dp),
            color = Color.White
        )
    }
}

enter image description here

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • it's not going to work for pagination lists, it's only for fixed size list – user924 Mar 24 '23 at 09:37
  • This breaks for cards taller than 500.dp doesn't it? I would just use a normal Column inside the card in this case instead (if it's expected there are not too many items on each body) – Robin Aug 31 '23 at 07:12
  • Yes, if cards are taller than 500.dp it limits maximum height to 500.dp but it's some arbitrary number, what you need to do with inner scrolalble Composable or LazyColumn is to not measure its height with Constraints.Infinity. To do that you set a maximum measurement bound for height that is finite bound. They simply added a Constraints.Infinity check for inner Composable to not let it scroll forever. – Thracian Aug 31 '23 at 07:19
  • This code is especially useful when you don't know the height of grid, LazyColumn to not limit them smaller then they should be and of let them to be measured between 0 and some finite number since their content is not infinite. Or when you have inner LazyColumn that you wish to scroll items, so you limit height and they become scrollable – Thracian Aug 31 '23 at 07:20
0

I think problem there

                    LazyColumn(){
                        itemsIndexed(listOf(1,2,3)){ index, item ->
                            ManageCredentialItem()
                        }
                    }

You cannot nest LasyColumn inside LazyColumn, but you can replace it like this:

Column {
    for (index, item in listOf(1, 2, 3).withIndex()) {
        ManageCredentialItem()
    }
}
Yoloroy
  • 185
  • 9
  • This won't make it Lazy – BraveEvidence Jan 21 '23 at 11:45
  • I have found related question, maybe comments under answer from there will help you. https://stackoverflow.com/questions/73609560/when-adding-lazy-column-inside-another-lazy-column-in-android-jetpack-compose-it – Yoloroy Jan 21 '23 at 16:43