Step 7 of 15
LazyColumn, LazyRow, LazyVerticalGrid, items(), keys for stable identity
Lazy layouts — LazyColumn, LazyRow, LazyVerticalGrid, stable keys
A LazyColumn renders only the items currently visible on screen. This is efficient for long lists — it does not create thousands of composables up front.
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@Composable
fun NameList(names: List<String>) {
LazyColumn {
items(names) { name ->
Text(
text = name,
modifier = Modifier.padding(16.dp)
)
}
}
}
// names = ["Ada", "Alan", "Grace"]
// Rendered:
// Ada
// Alan
// Grace
items(names) — loops over the list{ name -> ... } describes how to draw one itemUse itemsIndexed when you need both the index and the value:
LazyColumn {
itemsIndexed(names) { index, name ->
Text("$index: $name")
}
}
// Rendered:
// 0: Ada
// 1: Alan
// 2: Grace
When a list changes (insert, delete, reorder), give each item a stable key. Compose then moves the existing item instead of recreating it, which preserves animations and state.
data class Task(val id: Int, val title: String)
LazyColumn {
items(tasks, key = { it.id }) { task ->
Text(task.title)
}
}
Always use a unique, stable value for the key — never the item's position.
LazyRow works exactly like LazyColumn, but scrolls horizontally:
import androidx.compose.foundation.lazy.LazyRow
@Composable
fun ChipRow(tags: List<String>) {
LazyRow(
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(tags, key = { it }) { tag ->
AssistChip(onClick = {}, label = { Text(tag) })
}
}
}
For a grid of equal-sized cells, use LazyVerticalGrid:
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
@Composable
fun PhotoGrid(photos: List<Int>) {
LazyVerticalGrid(
columns = GridCells.Fixed(3), // 3 columns
contentPadding = PaddingValues(4.dp)
) {
items(photos, key = { it }) { photo ->
Image(
painter = painterResource(photo),
contentDescription = null,
modifier = Modifier.aspectRatio(1f)
)
}
}
}
Add space with contentPadding (space around the whole list) and Arrangement.spacedBy (space between items):
LazyColumn(
contentPadding = PaddingValues(vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(tasks, key = { it.id }) { task ->
TaskRow(task)
}
}
@Composable
fun TaskListScreen(tasks: List<Task>) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(tasks, key = { it.id }) { task ->
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
) {
Text("#${task.id}")
Spacer(Modifier.width(12.dp))
Text(task.title)
}
}
}
}
Build a LazyColumn of 20 numbered items. Give each a stable key. Add a button that inserts a new item at the top. Notice the animation — with stable keys, the new item slides in instead of flashing.