Instruction
Understanding Lazy Lists
When rendering a large data set with an unknown number of items, you’ll run into many performance issues if you use a regular Row/Column layout because it will compose all the items, whether they’re visible on the screen or not.
In the XML world, to keep things performing well and efficiently, RecyclerView renders only the elements that are visible on the screen. When the user scrolls, items that go off-screen are recycled into a pool, and new items are rendered using the same pool.
As their names suggest, LazyRow and LazyColumn composables support lazy loading, which means they load data only when it’s needed. When you use LazyColumn or LazyRow, the framework composes only the elements that can be shown on the screen. When you scroll, new elements are composed, and the old ones are disposed of. When you scroll back, the old elements are recomposed. Jetpack Compose doesn’t need a recycled ViewHolder pool because its recomposition handles caching more efficiently.
Exploring LazyListScope
Before working with the lazy composables, its important to understand how they differ from regular list composables.
If you look at the signature of LazyColumn and LazyRow:
@Composable
fun LazyColumn(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
verticalArrangement: Arrangement.Vertical =
if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
content: LazyListScope.() -> Unit
)
@Composable
fun LazyRow(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
horizontalArrangement: Arrangement.Horizontal =
if (!reverseLayout) Arrangement.Start else Arrangement.End,
verticalAlignment: Alignment.Vertical = Alignment.Top,
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
content: LazyListScope.() -> Unit
)
You notice they both accept a content parameter at the end representing the contents of a list. Unlike other layout composables that expect a @Composable instance, lazy composables offer a DSL from the LazyListScope.
Note: DSL, or Domain Specific Language, is a language feature available in Kotlin that lets you create instructions to solve specific problems. Kotlin uses type-safe builders to create a DSL that fits perfectly for building complex hierarchical data structures in a semi-declarative way.
The LazyListScope plays the role of a receiver scope in LazyRow and LazyColumn.
@LazyScopeMarker
interface LazyListScope {
fun item(key: Any? = null, content: @Composable LazyItemScope.() -> Unit)
fun items(
count: Int,
key: ((index: Int) -> Any)? = null,
itemContent: @Composable LazyItemScope.(index: Int) -> Unit
)
@ExperimentalFoundationApi
fun stickyHeader(
key: Any? = null,
content: @Composable LazyItemScope.() -> Unit
)
}
Here’s a breakdown of the snippet above:
- The
itemreceiver allows adding a singlecomposableitem into the lazy layout. You can callitemas many times as you want to add multiple items, but if you want to add a collection directly, use theitemsoption below. - The
itemsreceiver expects a count of items instead of defining the content of every item individually. Here, you pass the list length and create the specifications for every item. -
stickyHeaderadds a sticky item at the top. It remains pinned even when scrolling and stays put until the next header takes its place. This is very common in situations where you need some categorization, like a contacts app, based on alphabetical sorting.
Using a LazyRow
The simplest way to use a LazyRow is as follows:
LazyRow {
item {
Text("first item")
}
item {
Text("second item")
}
}
Here, you created a basic row and used the item receiver to add individual items to the lazy row.
To add multiple items, you would use the items receiver as follows:
LazyRow {
items(15) { index ->
Text("Item: $index")
}
}
In the snippet above, you added the items receiver, which takes in the list size and renders 15 text composables using the index.
A more realistic example of a lazy row would be the following:
LazyRow {
items(pictures) { picture ->
CarouselItem(picture)
}
}
Here, you render a carousel of pictures by passing the picture list to the items receiver and composing a CarouselItem for each picture in the list.
Using a LazyColumn
You use LazyColumn much like you use LazyRow.
LazyColumn {
item {
Header()
}
items(posts) { post ->
PostItem(post)
}
item {
Footer()
}
}
In the snippet above, you used:
- The
itemreceiver to render theHeaderandFootercomposables. - The
itemsreceiver to render thePostItemcomposables.
The following demo will show the lazy row and lazy column composables at work.