I am trying to build a screen that contains nested lists as in the image below.
The code that builds the preview above is:
@Preview
@Composable
fun ChannelBountiesCardPreview() {
Column {
Text(
text = "ACS Microfinance - *614*435# - NG".uppercase(),
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(id = R.dimen.margin_13)),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.End
)
repeat(3) {
BountyCardPreview()
}
}
}
@Preview
@Composable
fun BountyCardPreview() {
val margin13 = dimensionResource(id = R.dimen.margin_13)
val margin8 = dimensionResource(id = R.dimen.margin_8)
Column(modifier = Modifier.background(color = colorResource(id = R.color.colorSurface)).padding(vertical = margin8)) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = margin13),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "Check Balance",
modifier = Modifier
.padding(top = margin8, bottom = margin8, end = margin13),
style = MaterialTheme.typography.body1
)
Text(
text = "USD $1",
modifier = Modifier
.padding(top = margin8, bottom = margin8),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Medium
)
}
HorizontalImageTextView(
drawable = R.drawable.ic_error,
stringRes = R.string.bounty_transaction_failed,
modifier = Modifier.padding(start = margin13, end = margin13, top = 5.dp, bottom = dimensionResource(id = R.dimen.margin_10)),
MaterialTheme.typography.caption
)
}
}
@Preview
@Composable
fun BountiesPreview() {
AppTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
LazyColumn {
items(5) {
ChannelBountiesCardPreview()
}
}
}
}
}
While the preview renders successfully, when the app is run, it crashes with the following error:
java.lang.IllegalStateException: Vertically scrollable component was measured with an infinity maximum height constraints, which i
s disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to a
dd 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 t
he hierarchy above the scrolling container.
The code for the screen is:
@Composable
fun BountyList(bountyViewModel: BountiesViewModel) {
val bountiesState by bountyViewModel.bountiesState.collectAsState()
AppTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
LazyColumn {
items(bountiesState.bounties) { channelBounty ->
ChannelBountyCard(channelBounty = channelBounty)
}
}
}
}
}
@Composable
fun ChannelBountyCard(channelBounty: ChannelBounties) {
Column {
Text(
text = channelBounty.channel.ussdName.uppercase(),
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(id = R.dimen.margin_13)),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.End
)
channelBounty.bounties.forEach {
BountyCard(bounty = it)
}
}
}
@Composable
fun BountyCard(bounty: Bounty) {
val context = LocalContext.current
val margin8 = dimensionResource(id = R.dimen.margin_8)
val margin13 = dimensionResource(id = R.dimen.margin_13)
val bountyState = getBountyState(bounty)
val strikeThrough = TextStyle(
fontFamily = Brutalista,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
textDecoration = TextDecoration.LineThrough
)
Column(
modifier = Modifier
.background(color = colorResource(id = bountyState.color))
.padding(vertical = margin8)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = margin13),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = bounty.generateDescription(context),
modifier = Modifier
.padding(top = margin8, bottom = margin8, end = margin13),
style = if (bountyState.isOpen) MaterialTheme.typography.body1 else strikeThrough
)
Text(
text = stringResource(R.string.bounty_amount_with_currency, bounty.action.bounty_amount),
modifier = Modifier
.padding(top = margin8, bottom = margin8),
style = if (bountyState.isOpen) MaterialTheme.typography.body1 else strikeThrough,
fontWeight = FontWeight.Medium
)
}
if (bountyState.msg != 0)
HorizontalImageTextView(
drawable = bountyState.icon,
stringRes = bountyState.msg,
modifier = Modifier
.padding(start = margin13, end = margin13, top = 5.dp, bottom = dimensionResource(id = R.dimen.margin_10)),
MaterialTheme.typography.caption
)
}
}
@Composable
internal fun HorizontalImageTextView(
@DrawableRes drawable: Int,
@StringRes stringRes: Int,
modifier: Modifier = Modifier, textStyle: TextStyle
) {
Row(horizontalArrangement = Arrangement.Start, modifier = modifier) {
Image(
painter = painterResource(id = drawable),
contentDescription = null,
modifier = Modifier.align(Alignment.CenterVertically),
)
Text(
text = Html.fromHtml(stringResource(id = stringRes), HtmlCompat.FROM_HTML_MODE_LEGACY).toString(),
style = textStyle,
modifier = Modifier
.padding(start = dimensionResource(id = R.dimen.margin_13))
.align(Alignment.CenterVertically),
color = colorResource(id = R.color.offWhite)
)
}
}
None of the existing solutions for nested loops work. Most of them deal with expandable lists while my use case needs the list of items to be visible at all times.