0

I'm new to Jetpack Compose and I'm not quite sure how to do what I need. In the screen below, I want to scroll the whole screen and not just the list at the bottom and when the scroll reaches the end of the list below, it still applies the paging library and goes to get more elements. I managed to get the Paging Library to work and the scroll in the list below too, but I can't make the rest of the page elements scroll as well - this is because only the list has scroll and not the rest of the page. Whenever I'm trying to do that, I get the following crash:

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.

and I don't really know why.

I leave you the code below and two screenshots: the first is the current state, where I can only scroll through the list. The second is what I intend, which is to scroll the entire page.

@Edit: I was able to implement all screen scroll with fixed height on the children lazy column, but that is not what I want.

enter image description here

enter image description here

@Composable
@ExperimentalFoundationApi
private fun MainActivityLayout(navController: NavHostController) {
    LazyColumn(
        modifier = Modifier
            .paint(
                painter = painterResource(id = R.drawable.main_background),
                contentScale = ContentScale.FillBounds
            )
            .fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        item {
            HeightSpacer(Dimen40)

            Image(
                painter = painterResource(id = R.drawable.ic_clearjobs_logo_2x),
                contentDescription = null
            )

            HeightSpacer(Dimen47)
            Navigation(navController = navController)
        }
    }
}

@Composable
@ExperimentalFoundationApi
fun JobOpeningsScreen(viewModel: JobOpeningsViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()

    Column {
        ClearJobsScreenTitle(
            lightTitle = stringResource(id = R.string.job_openings_light_title),
            boldTitle = stringResource(id = R.string.job_openings_bold_title)
        )

        HeightSpacer(Dimen60)
        Row {
            CategoryButton()
            WidthSpacer(Dimen2)
            OrderByButton()
        }
        HeightSpacer(Dimen30)
        SearchTextField()
        HeightSpacer(Dimen60)

        when (uiState) {
            is BaseViewState.Data -> JobOpeningsContent(
                viewState = uiState.cast<BaseViewState.Data<JobOpeningsViewState>>().value
            )
            is BaseViewState.Loading -> {
                LoadingView()
            }
            else -> {}
        }

        LaunchedEffect(key1 = viewModel, block = {
            viewModel.onTriggerEvent(JobOpeningsEvent.LoadJobOffers)
        })
    }
}

@Composable
fun JobOpeningsContent(viewState: JobOpeningsViewState) {
    val pagingItems = rememberFlowWithLifecycle(viewState.pagedData).collectAsLazyPagingItems()

    SwipeRefresh(
        state = rememberSwipeRefreshState(
            isRefreshing = pagingItems.loadState.refresh == LoadState.Loading
        ),
        onRefresh = { pagingItems.refresh() },
        indicator = { state, trigger ->
            SwipeRefreshIndicator(
                state = state,
                refreshTriggerDistance = trigger,
                scale = true
            )
        },
        content = {
            LazyColumn(
                modifier = Modifier.width(Dimen320),
                verticalArrangement = Arrangement.spacedBy(Dimen30)
            ) {
                items(pagingItems.itemCount) { index ->
                    pagingItems[index]?.let {
                        JobOpeningsRow(dto = it)
                    }
                }

                if (pagingItems.loadState.append == LoadState.Loading) {
                    item {
                        Box(
                            Modifier
                                .padding(24.dp)
                        ) {
                            CircularProgressIndicator(Modifier.align(Alignment.Center))
                        }
                    }
                }
            }
        }
    )
}
R0ck
  • 409
  • 1
  • 15
  • Columns are not scrollable by default. Have you tried adding a `verticalScroll` modifier to the parent `Column` – Rafsanjani Aug 09 '22 at 15:13
  • @Rafsanjani do you mean for the column in MainActivityLayout? If you mean that, yes, I tried, and it throws the error that I wrote above. – R0ck Aug 09 '22 at 15:23

1 Answers1

0

I found the solution to this problem, although it is not 100% and in terms of code it is not as good as I would like.

The error speaks for itself, we can't have infinite vertical scroll, Jetpack Compose doesn't allow it. I had the option of putting a fixed height on the Lazy Column of my list, but it wasn't what I wanted and it didn't work properly. The solution was to put everything inside a single LazyColumn and remove the Column from MainActivity, using a Box element and contentAlignment. I leave you below the final code that I used to solve the problem.

MainScreen function that before was MainActivityLayout function:

@Preview
@Composable
@ExperimentalFoundationApi
fun MainScreen() {
    val navController = rememberNavController()

    val topLevelDestinations = listOf(
        NavigationItem.JobOpenings,
        NavigationItem.Profile,
        NavigationItem.About
    )

    val isTopLevelDestination =
        navController
            .currentBackStackEntryAsState()
            .value
            ?.destination
            ?.route in topLevelDestinations.map { it.route }

    val backStackEntryState = navController.currentBackStackEntryAsState()

    Scaffold(
        bottomBar = {
            if (isTopLevelDestination) {
                BottomNavBar(
                    navController = navController,
                    backStackEntryState = backStackEntryState,
                    bottomNavItems = topLevelDestinations
                )
            }
        }
    ) {
        Box(
            modifier = Modifier
                .paint(
                    painter = painterResource(id = R.drawable.main_background),
                    contentScale = ContentScale.FillBounds
                )
                .fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Navigation(navController = navController)
        }
    }
}

New JobOpenings fun that is mixed with old JobOpeningsContent function:

@Composable
@ExperimentalFoundationApi
fun JobOpeningsScreen(viewModel: JobOpeningsViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()

    when (uiState) {
        is BaseViewState.Data -> {
            val pagedData = uiState.cast<BaseViewState.Data<JobOpeningsViewState>>().value.pagedData
            val pagingItems = rememberFlowWithLifecycle(pagedData).collectAsLazyPagingItems()

            SwipeRefresh(
                state = rememberSwipeRefreshState(
                    isRefreshing = pagingItems.loadState.refresh == LoadState.Loading
                ),
                onRefresh = { pagingItems.refresh() },
                indicator = { state, trigger ->
                    SwipeRefreshIndicator(
                        state = state,
                        refreshTriggerDistance = trigger,
                        scale = true
                    )
                },
                content = {
                    LazyColumn(
                        modifier = Modifier
                            .width(Dimen320),
                        verticalArrangement = Arrangement.spacedBy(Dimen30)
                    ) {
                        item {
                            ScreenHeader(
                                lightTitle = stringResource(id = R.string.job_openings_light_title),
                                boldTitle = stringResource(id = R.string.job_openings_bold_title)
                            )

                            HeightSpacer(Dimen60)
                            Row {
                                CategoryButton()
                                WidthSpacer(Dimen2)
                                OrderByButton()
                            }
                            HeightSpacer(Dimen30)
                            SearchTextField()
                            HeightSpacer(Dimen60)
                        }

                        items(pagingItems.itemCount) { index ->
                            pagingItems[index]?.let {
                                JobOpeningsRow(dto = it)
                            }
                        }

                        if (pagingItems.loadState.append == LoadState.Loading) {
                            item {
                                Box(Modifier.padding(Dimen24)) {
                                    CircularProgressIndicator(Modifier.align(Alignment.Center))
                                }
                            }
                        }
                    }
                }
            )
        }
        is BaseViewState.Loading -> LoadingView()
        else -> {}
    }

    LaunchedEffect(key1 = viewModel, block = {
        viewModel.onTriggerEvent(JobOpeningsEvent.LoadJobOffers)
    })
}

@ExperimentalFoundationApi
@Preview
@Composable
fun JobOpenings() {
    JobOpeningsScreen()
}

Problems that I found with this solution:

  • LoadingView appears at the top of the screen instead at the top of the list.

If anyone has any suggestion to improve this, I am open to it. This works perfectly with Paging Library + Swipe Refresh (Accompanist) and full page scroll.

R0ck
  • 409
  • 1
  • 15