100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Kotlin

LazyGrid and Item Keys

Explore LazyVerticalGrid and LazyHorizontalGrid for building grid layouts in Compose, and why stable item keys are essential for correctness.

Lists & Lazy LayoutsIntermediate9 min readJul 8, 2026
Analogies

LazyGrid and Item Keys

LazyVerticalGrid and LazyHorizontalGrid extend the lazy-loading model of LazyColumn/LazyRow to two-dimensional grid layouts — think a photo gallery, a product catalog, or an app-drawer grid. Like their single-axis counterparts, they only compose cells near the visible viewport, but they add a columns (or rows, for the horizontal variant) parameter that determines how many cells fit across the grid, either as a fixed count via GridCells.Fixed(n) or as an adaptive count via GridCells.Adaptive(minSize) that fits as many columns as possible given a minimum cell width.

🏏

Cricket analogy: Just as a stadium's giant screen only renders the seating sections currently in view rather than every seat in the ground, LazyVerticalGrid only composes cells near the visible viewport, with GridCells.Fixed(n) or Adaptive(minSize) setting how many seats fit per row.

GridCells.Fixed vs GridCells.Adaptive

GridCells.Fixed(3) always lays out exactly three columns regardless of screen width, which is predictable but can look cramped on large tablets or overly narrow on small phones. GridCells.Adaptive(minSize = 120.dp) instead calculates how many columns of at least 120dp can fit in the available width, so the same code naturally produces two columns on a narrow phone and five or six on a tablet — a common technique for building genuinely responsive grid UIs without writing separate layouts per screen size.

🏏

Cricket analogy: GridCells.Fixed(3) is like always fielding exactly three slips regardless of pitch conditions — rigid but predictable — while GridCells.Adaptive(120.dp) is like a captain who adjusts the slip cordon size based on how much swing is on offer.

kotlin
@Composable
fun PhotoGalleryGrid(photos: List<Photo>, onPhotoClick: (Photo) -> Unit) {
    LazyVerticalGrid(
        columns = GridCells.Adaptive(minSize = 120.dp),
        contentPadding = PaddingValues(4.dp),
        horizontalArrangement = Arrangement.spacedBy(4.dp),
        verticalArrangement = Arrangement.spacedBy(4.dp)
    ) {
        items(
            items = photos,
            key = { photo -> photo.id },
            span = { photo -> GridItemSpan(if (photo.isFeatured) 2 else 1) }
        ) { photo ->
            AsyncImage(
                model = photo.thumbnailUrl,
                contentDescription = photo.caption,
                contentScale = ContentScale.Crop,
                modifier = Modifier
                    .aspectRatio(1f)
                    .clip(RoundedCornerShape(6.dp))
                    .clickable { onPhotoClick(photo) }
            )
        }
    }
}

Why Item Keys Matter Even More in Grids

Everything said about keys for LazyColumn applies to grids, but grids amplify the stakes: because span logic (via GridItemSpan) can change which row a given cell lands in, and because reflow after insertion/removal affects both row and column position simultaneously, a missing or unstable key makes it far more likely that Compose reattributes a cell's remembered state — such as a selection checkbox in a multi-select photo picker — to visually the wrong photo after the underlying list changes. Always derive the key from a genuinely unique, stable property of the data, such as a database ID or URL, never from a mutable field like a display order index.

🏏

Cricket analogy: If a scorecard app reorders batters by current strike rate instead of a stable player ID, a fielding-position marker can attach to the wrong batter after a shuffle, just as an unstable grid key misattributes a photo's selection checkbox after reflow.

GridItemSpan lets a single item occupy multiple columns — useful for 'featured' or 'hero' cells inside an otherwise uniform grid, such as a promoted product spanning two columns in an e-commerce catalog.

A frequent mistake is using the list index as the key (key = { index -> index }), which defeats the entire purpose of keys — it's mathematically identical to providing no key at all, since position-based identity is exactly the default behavior Compose already falls back to.

  • LazyVerticalGrid and LazyHorizontalGrid apply LazyColumn's lazy-composition model to two-dimensional grid layouts.
  • GridCells.Fixed(n) sets an exact column count; GridCells.Adaptive(minSize) computes a responsive column count based on available width.
  • GridItemSpan lets individual items occupy multiple columns for featured or hero-style grid cells.
  • Stable, data-derived keys (like a database ID) are essential for correctly tracking item identity across span changes, insertion, and removal.
  • Using the list index itself as a key provides no real benefit over omitting the key entirely.
  • Grids amplify the consequences of missing keys because both row and column position can shift simultaneously on data changes.

Practice what you learned

Was this page helpful?

Topics covered

#Kotlin#AndroidWithJetpackComposeStudyNotes#MobileDevelopment#LazyGridAndItemKeys#LazyGrid#Item#Keys#GridCells#StudyNotes#SkillVeris