34

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.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Aiman Muzafar
  • 385
  • 1
  • 4
  • 11

3 Answers3

44

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

nglauber
  • 18,674
  • 6
  • 70
  • 75
3

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

Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • 2
    Thank you so much for taking the time to provide a detailed solution. Although the solution is accurate, but it doesn't scroll the entire activity. So I'm going to merge the solution provided by you (the grid part) and @nglauber. Thanks again. – Aiman Muzafar Apr 02 '21 at 16:23
  • Quick Question: Since here LazyVerticalGrid is wrapped in a simple Column, do you still get recycling advantage of LazyVerticalGrid? Or Column will try to populate all content including LazyVerticalGrid on first render? – HBB20 Aug 26 '21 at 17:03
3

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

Cherif Diallo
  • 311
  • 2
  • 3
  • 2
    Can you explain what's the difference between accepted answer and your answer? – Ranjithkumar Nov 23 '21 at 13:02
  • 1
    Unlike the accepted answer, my answer 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, my answer addresses the scenario of nested scrolling in the same direction. Finally, in my example, the bottom list can scroll independently of the top one. Thanks for your clarification comment :)! – Cherif Diallo Nov 23 '21 at 14:13
  • ohh ok. Thanks for your clarification. I thought it's same as accepted answer. – Ranjithkumar Nov 24 '21 at 06:59
  • 1
    @CherifDiallo: Wow, that's useful clarification. Could you _edit_ that into your answer, so that future readers aren't confused by the difference? Thank you. – Jeremy Caney Nov 25 '21 at 01:24