Exploring lists with Compose
In the previous section, we built a Compose-based screen that features a list of restaurants. However, if you run the application or preview the screen in interactive mode, you will notice that the list doesn't scroll. This is a huge inconvenience that we will address in this section by adding scroll capabilities to our Column
composable.
Next, we will specify why Column
is suited for static content, whereas if the list is large and its size is dynamic or dictated by the server's response, we should use lazy composables. We will explore a variety of lazy composables and understand why they are better suited for large lists.
To summarize, this section will cover the following topics:
- Adding scrolling to the
Column
composable - Introducing lazy composables
- Using
LazyColumn
to display restaurants
Let's start by adding scrolling capabilities to our RestaurantsScreen
composable.
Adding scrolling to the Column composable
Our list of restaurants is long, and it can't scroll. This is a bad user experience, so let's fix it.
Let's make the Column
scrollable by passing a Modifier.verticalScroll
modifier that receives a ScrollState
:
@Composable fun RestaurantsScreen() { Column(Modifier.verticalScroll(rememberScrollState())) { ... } }
We want the scrolling position to be retained across recompositions. That's why, by passing rememberScrollState
to the verticalScroll
modifier, we ensure that every time the UI recomposes, the scroll state is remembered and retained. The rememberScrollState
persistence mechanism is similar to the remember { }
block, which we used previously to retain the TextField
's state across recompositions.
Now, you can Run the app or preview it in Interactive mode and check out the scrolling effect.
However, we have one final issue with our Column
that is related to how Column
lays and composes its elements. Let's dive into that now and try to find a better alternative.
Introducing lazy composables
Let's take a short break from our restaurant app and try to think of a better way of handling large lists. Using Row
or Column
for displaying long lists of items, or maybe a list of unknown size, can prove detrimental to your UI and impact your app's performance. This happens because Row
and Column
render or lay all their children out, whether they are visible or not. They are good for displaying static content, yet passing a large list can cause your UI to become laggy or even unusable.
Two lazy composables called LazyColumn
and LazyRow
come to your rescue since they only compose or output those items that are currently visible on the screen, hence the term lazy. So, as you can see, they are somehow similar to the old RecyclerView
.
As the only difference between Row
and Column
was the way children were laid out on the screen – horizontally or vertically – the same thing applies with LazyRow
and LazyColumn
. These lazy composables lay their children out horizontally or vertically and provide scrolling capabilities out of the box. As they only render the visible items, lazy composables are a much better fit for large lists.
Yet, lazy composables are different than the regular composables that we've used so far. That's mainly because instead of accepting @Composable
content, they expose a domain-specific language (DSL) defined by a LazyListScope
block:
@Composable fun LazyColumn( ... content: LazyListScope.() -> Unit ) { … }
The LazyListScope
DSL allows us to describe the item contents that we want to be displayed as part of the list. The most commonly used ones are item()
and items()
. Such example usage of LazyColumn
that makes use of DSL is as follows:
LazyColumn { item() { Text(text = "Custom header item") } items(myLongList) { myItem -> MyComposable(myItem) } item(2) { Text(text = "Custom footer item") } }
item()
adds a single composable element to the list, while items()
can receive not only a standalone list of content such as myLongList
but also an Int
, which will add the same item multiple times.
The code that we featured previously should render a vertical list that contains the following:
- A header
Text
composable - A list of
MyComposable
composables that are the same size asmyLongList
- Two
Text
footer composables
Returning from the DSL world, a noteworthy argument for the lazy composables is contentPadding
, which allows you to define horizontal/vertical padding surrounding your list. This argument expects a PaddingValues
object – we will use it soon; don't worry!
Now, we will soon receive the restaurants from a remote server, which means we don't know the size of the list, so it's time to implement such a lazy composable in our Restaurants application as well.
Using LazyColumn to display restaurants
We are currently using Column
to display our dummyRestaurants
list. We know why that's not the best practice, so to optimize our UI for dynamic content, we will replace it with LazyColumn
so that we can continue displaying the restaurants vertically.
Go back to the RestaurantsScreen.kt
file and, inside of the RestaurantScreen
composable, replace the Column
composable with LazyColumn
:
@Composable fun RestaurantsScreen() { LazyColumn( contentPadding = PaddingValues( vertical = 8.dp, horizontal = 8.dp)) { items(dummyRestaurants) { restaurant -> RestaurantItem(restaurant) } } }
We've used its DSL and specified the items
properties that should populate our LazyColumn
by passing the dummyRestaurants
list. We obtained access to each item as a restaurant of type Restaurant
and rendered it through a RestaurantItem
composable.
We also added additional padding through the contentPadding
argument to our LazyColumn
by passing a PaddingValues
object where we configured the vertical and horizontal padding.
You can now Run the app and check out the scrolling effect. In our case, the output is the same, yet if we were to test the app with a very long list of restaurants, we would have a much smoother scroll effect and a better UI experience with LazyColumn
than with Column
.
We've done it! We've built our first Compose-based app from scratch while exploring tons of composable functions. We've added a list that scrolls beautifully, and we can now be proud of the result!