Home > front end >  Jetpack Compose LazyColumn inside Scrollabe Column
Jetpack Compose LazyColumn inside Scrollabe Column

Time:04-16

here's my situation: I have to show in my app a detail of a record I receive from API. Inside this view, I may or may not need to show some data coming from another viewmodel, based on a field.

Here my code:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ViewDetail(viewModel: MainViewModel, alias: String?, otherViewModel: OtherViewModel) {
    viewModel.get(alias)

    Scaffold {
        val isLoading by viewModel.isLoading.collectAsState()
        val details by viewModel.details.collectAsState()

        when {
            isLoading -> LoadingUi()
            else -> Details(details, otherViewModel)
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun Details(details: Foo?, otherViewModel: OtherViewModel) {
    details?.let { sh ->
        val scrollState = rememberScrollState()

        Column(
            modifier = Modifier
                .fillMaxSize()
                .verticalScroll(scrollState),
        ) {
            Text(sh.title, fontSize = 24.sp, lineHeight = 30.sp)

            Text(text = sh.description)

            if (sh.other.isNotEmpty()) {
                otherViewModel.load(sh.other)
                val others by otherViewModel.list.collectAsState()

                Others(others)
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun Others(others: Flow<PagingData<Other>>) {
    val items: LazyPagingItems<Other> = others.collectAsLazyPagingItems()

    LazyColumn(
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentHeight(),
        contentPadding = PaddingValues(16.dp),
    ) {
        items(items = items) { item ->
            if (item != null) {
                Text(text = item.title, fontSize = 24.sp)

                Spacer(modifier = Modifier.height(4.dp))

                Text(text = item.description)
            }
        }

        if (items.itemCount == 0) {
            item { EmptyContent() }
        }
    }
}

All the description here may be very long, both on the main Details body or in the Others (when present), so here's why the scroll behaviour requested.

Problem: I get this error:

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()).

I hoped that .wrapContentHeight() inside LazyColumn would do the trick, but to no avail.

Is this the right way to do it?

Context: all packages are updated to the latest versions available on maven

CodePudding user response:

You can use a system like the following

@Composable
fun Test() {
    Box(Modifier.systemBarsPadding()) {
        Details()
    }
}

@Composable
fun Details() {
    LazyColumn(Modifier.fillMaxSize()) {
        item {
            Box(Modifier.background(Color.Cyan).padding(16.dp)) {
                Text(text = "Hello World!")
            }
        }
        item {
            Box(Modifier.background(Color.Yellow).padding(16.dp)) {
                Text(text = "Another data")
            }
        }
        item {
            Others()
        }
    }
}

@Composable
fun Others() {
    val values = MutableList(50) { it }

    values.forEach {
        Box(
            Modifier
                .fillMaxWidth()
                .padding(16.dp)
        ) {
            Text(text = "Value = $it")
        }
    }
}

The result with scroll is:

This is the result

CodePudding user response:

The main idea here is to merge your Column with LazyColumn.

As your code is not runnable, I'm giving more a pseudo code, which should theoretically work.

Also calling otherViewModel.load(sh.other) directly from Composable builder is a mistake. According to thinking in compose, to get best performance your view should be side effects free. To solve this issue Compose have special side effect functions. Right now your code is gonna be called on each recomposition.

if (sh.other.isNotEmpty()) {
    LaunchedEffect(Unit) {
        otherViewModel.load(sh.other)
    }
}
val others by otherViewModel.list.collectAsState()

LazyColumn(
    modifier = Modifier
        .fillMaxSize()
        .wrapContentHeight(),
    contentPadding = PaddingValues(16.dp),
) {
    item {
        Text(sh.title, fontSize = 24.sp, lineHeight = 30.sp)

        Text(text = sh.description)
    }
    
    items(items = items) { item ->
        if (item != null) {
            Text(text = item.title, fontSize = 24.sp)

            Spacer(modifier = Modifier.height(4.dp))

            Text(text = item.description)
        }
    }

    if (items.itemCount == 0) {
        item { EmptyContent() }
    }
}
  • Related