What is the equivalent of [NestedScrollView + RecyclerView] or [Nested RecyclerView (Recycler inside another recycler) in Jetpack compose

Viewed 11286

I want to create the following layout in Jetpack compose. enter image description here

I've tried creating two lists inside a vertical scrollable Box but that's not possible as I got the this error: "java.lang.IllegalStateException: Nesting scrollable in the same direction layouts like ScrollableContainer and LazyColumn is not allowed. If you want to add a header before the list of items please take a look on LazyColumn component which has a DSL api which allows to first add a header via item() function and then the list of items via items()."

I've tried creating two different lists inside a parent list by using the following code, but that doesn't work either.

@Composable
fun MainList() {
    LazyColumn() {
        
        item {
            /* LazyRow code here */
        }
        
        item {
            /* LazyColumn code here */
        }
    }
}

Now I'm clueless about what else could I try to achieve two lists (one vertical and one horizontal) on the same activity and keep the activity vertically scrollable too.

3 Answers

I think the best option, would be if the LazyVerticalGrid allows some sort of expand logic on each item, but looks like it's not supported yet (beta-03).

So I'm leaving here my solution using one single LazyColumn for the entire list and LazyRow for "My Books" section.

LazyColumn(
    modifier = Modifier.fillMaxSize(),
) {
    // My Books section
    item {
        Column(modifier = Modifier.fillMaxWidth()) {
            Text("My Books")
            LazyRow {
                items(books) { item ->
                    // Each Item
                }
            }
        }

    }
    // Whishlisted Books title
    item {
        Text("Whishlisted Books", style = MaterialTheme.typography.h4)
    }
    // Turning the list in a list of lists of two elements each
    items(wishlisted.windowed(2, 2, true)) { item ->
        Row {
            // Draw item[0]
            // Draw item[1]
        }
    }
}

Here is my gist with the full solution and the result is listed below.

enter image description here

You can do something like:

   Column(Modifier.fillMaxWidth()) {

        LazyRow() {
            items(itemsList){
                  //.....
                }
        }

       LazyColumn() {
           items(itemsList2){
               //..
           }
       }
    }

enter image description here

or:

   Column(Modifier.fillMaxWidth()) {

        LazyRow() {
            items(itemsList){
               //....
            }
        }
       
       LazyVerticalGrid(cells = GridCells.Fixed(2)) {
           items(itemsList2.size){
               //....
           }
       }

    }

enter image description here

An alternative equivalent of nested RecyclerViews is nested LazyColumns, where the heights of the inner LazyColumns are specified or constant, and the inner LazyColumns are placed inside item {} blocks.

Unlike the accepted answer, this approach relies on the .height() modifier to avoid the "java.lang.IllegalStateException: Nesting scrollable in the same direction layouts like ScrollableContainer and LazyColumn is not allowed... " error. Also, this approach addresses the scenario of nested scrolling in the same direction.

Here is an example code and output.

    @Composable
    fun NestedLists() {
        LazyColumn(Modifier.fillMaxSize().padding(12.dp),
            horizontalAlignment = Alignment.CenterHorizontally) {
            //Header for first inner list
            item {
                Text(text = "List of numbers:", style = MaterialTheme.typography.h5)
            }
            // First, scrollable, inner list
            item {
                // Note the important height modifier.
                LazyColumn(Modifier.height(100.dp)){
                    val numbersList = (0 .. 400 step 4).toList()
                    itemsIndexed(numbersList) { index, multipleOf4 ->
                        Text(text = "$multipleOf4", style = TextStyle(fontSize = 22.sp, color = Color.Blue))
                    }
                }
            }

        // Header for second inner list
        item {
            Text(text = "List of letters:", style = MaterialTheme.typography.h5)
        }
        // Second, scrollable, inner list
        item {
            // Note the important height modifier.
            LazyColumn(Modifier.height(200.dp)) {
                val lettersList = ('a' .. 'z').toList()
                itemsIndexed(lettersList) { index, letter ->
                    Text(text = "$letter", style = TextStyle(color = Color.Blue, fontSize = 22.sp))
                }
            }
        }
    }
}

enter image description here

Related