Jetpack Compose LazyColumn

Jetpack Compose LazyColumn is a vertically scrolling list component that efficiently renders only the items currently visible on screen. Unlike the traditional RecyclerView, Jetpack Compose LazyColumn simplifies list creation with a declarative approach, eliminating the need for adapters and view holders. The Jetpack Compose LazyColumn component is designed to handle large datasets efficiently while providing smooth scrolling performance.

Key Properties of Jetpack Compose LazyColumn

1. State Management in Jetpack Compose LazyColumn

Jetpack Compose LazyColumn integrates seamlessly with Compose's state management system. Here's a quick example:

val listItems = remember { mutableStateOf(List(100) { "Item $it" }) }

LazyColumn {
    items(listItems.value) { item ->
        Text(
            text = item,
            modifier = Modifier.padding(16.dp)
        )
    }
}

The LazyColumn automatically updates when the list state changes, providing reactive UI updates without manual intervention.

2. Content Padding in Jetpack Compose LazyColumn

Jetpack Compose LazyColumn allows you to add padding around the entire content using the contentPadding parameter:

LazyColumn(
    contentPadding = PaddingValues(
        start = 16.dp,
        top = 8.dp,
        end = 16.dp,
        bottom = 8.dp
    )
) {
    items(100) { index ->
        Text("Item #$index", Modifier.padding(8.dp))
    }
}

This padding applies to the entire list content rather than individual items, making it perfect for accommodating system insets or design requirements.

3. Item Spacing in Jetpack Compose LazyColumn

The Jetpack Compose LazyColumn provides multiple ways to add spacing between items:

LazyColumn(
    verticalArrangement = Arrangement.spacedBy(8.dp)
) {
    items(100) { index ->
        Text("Item #$index")
    }
}

The verticalArrangement parameter with Arrangement.spacedBy() creates consistent spacing between all items in the LazyColumn.

4. Scrolling States and Control in Jetpack Compose LazyColumn

Jetpack Compose LazyColumn gives you fine-grained control over scrolling behavior:

val lazyListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()

LazyColumn(state = lazyListState) {
    items(100) { index ->
        Text("Item #$index", Modifier.padding(16.dp))
    }
}

Button(
    onClick = {
        coroutineScope.launch {
            // Scroll to item at position 10
            lazyListState.animateScrollToItem(10)
        }
    }
) {
    Text("Scroll to item #10")
}

The rememberLazyListState() function creates a state object that tracks scrolling position and provides methods for programmatic scrolling.

5. Item Keys in Jetpack Compose LazyColumn

Jetpack Compose LazyColumn supports item keys for efficient updates and animations:

data class Person(val id: Int, val name: String)

val people = List(100) { Person(it, "Person $it") }

LazyColumn {
    items(
        items = people,
        key = { person -> person.id }
    ) { person ->
        Text(person.name, Modifier.padding(16.dp))
    }
}

Providing a key function helps Jetpack Compose LazyColumn identify which items have changed, moved, or been removed, enabling more efficient updates and animations.

6. Sticky Headers in Jetpack Compose LazyColumn

Jetpack Compose LazyColumn supports sticky headers that remain at the top during scrolling:

LazyColumn {
    stickyHeader {
        Text(
            "Sticky Header",
            modifier = Modifier
                .fillMaxWidth()
                .background(MaterialTheme.colorScheme.primary)
                .padding(16.dp),
            color = MaterialTheme.colorScheme.onPrimary
        )
    }
    
    items(100) { index ->
        Text("Item #$index", Modifier.padding(16.dp))
    }
}

Sticky headers in Jetpack Compose LazyColumn are perfect for section titles or category headers that should remain visible while scrolling through a section.

7. Loading More Items in Jetpack Compose LazyColumn

Implement infinite scrolling in Jetpack Compose LazyColumn with the onReachEnd callback:

val lazyListState = rememberLazyListState()
val isLoading = remember { mutableStateOf(false) }
val items = remember { mutableStateOf(List(20) { "Initial Item $it" }) }

LaunchedEffect(lazyListState) {
    snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo }
        .collect { visibleItems ->
            // Check if last item is visible
            if (!isLoading.value && 
                visibleItems.any { it.index == items.value.size - 1 }) {
                isLoading.value = true
                // Simulate loading more items
                delay(1000)
                val newItems = List(10) { "New Item ${items.value.size + it}" }
                items.value = items.value + newItems
                isLoading.value = false
            }
        }
}

LazyColumn(state = lazyListState) {
    items(items.value) { item ->
        Text(item, Modifier.padding(16.dp))
    }
    
    if (isLoading.value) {
        item {
            CircularProgressIndicator(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
                    .wrapContentWidth(Alignment.CenterHorizontally)
            )
        }
    }
}

This pattern allows Jetpack Compose LazyColumn to load additional items when the user scrolls near the end of the list, providing a seamless infinite scrolling experience.

Complete Example of Jetpack Compose LazyColumn

Here's a full implementation of a Jetpack Compose LazyColumn with various features:

Example 1

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

@Composable
fun JetpackComposeLazyColumnDemo() {
    val coroutineScope = rememberCoroutineScope()
    val lazyListState = rememberLazyListState()
    
    // Sample data
    data class Contact(val id: Int, val name: String, val phone: String)
    
    val alphabet = ('A'..'Z').toList()
    val contacts = remember {
        alphabet.flatMap { letter ->
            List(5) { index ->
                Contact(
                    id = letter.code * 100 + index,
                    name = "$letter Name $index",
                    phone = "555-${letter.code}-${1000 + index}"
                )
            }
        }.sortedBy { it.name }
    }
    
    // Group contacts by first letter
    val groupedContacts = contacts.groupBy { it.name.first() }
    
    // Selected contact state
    var selectedContact by remember { mutableStateOf<Contact?>(null) }
    
    Column(modifier = Modifier.fillMaxSize()) {
        // Show selected contact details
        selectedContact?.let { contact ->
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(modifier = Modifier.padding(16.dp)) {
                    Text(
                        text = contact.name,
                        fontSize = 20.sp,
                        fontWeight = FontWeight.Bold
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Text(text = "Phone: ${contact.phone}")
                    Spacer(modifier = Modifier.height(8.dp))
                    Button(
                        onClick = { selectedContact = null },
                        modifier = Modifier.align(Alignment.End)
                    ) {
                        Text("Close")
                    }
                }
            }
        }
        
        // Quick scroll buttons
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .horizontalScroll(rememberScrollState())
                .padding(8.dp),
            horizontalArrangement = Arrangement.spacedBy(4.dp)
        ) {
            alphabet.forEach { letter ->
                if (groupedContacts.containsKey(letter)) {
                    Button(
                        onClick = {
                            coroutineScope.launch {
                                // Find the position of the first item that starts with this letter
                                val position = contacts.indexOfFirst { it.name.startsWith(letter) }
                                if (position >= 0) {
                                    lazyListState.animateScrollToItem(
                                        index = position + groupedContacts.keys
                                            .filter { it < letter }
                                            .count() // Add the number of headers before this section
                                    )
                                }
                            }
                        },
                        modifier = Modifier.size(40.dp),
                        contentPadding = PaddingValues(0.dp)
                    ) {
                        Text(letter.toString())
                    }
                }
            }
        }
        
        // LazyColumn with contacts
        LazyColumn(
            state = lazyListState,
            modifier = Modifier.fillMaxSize(),
            contentPadding = PaddingValues(bottom = 16.dp),
            verticalArrangement = Arrangement.spacedBy(1.dp)
        ) {
            groupedContacts.forEach { (letter, contactsInGroup) ->
                stickyHeader {
                    Box(
                        modifier = Modifier
                            .fillMaxWidth()
                            .background(MaterialTheme.colorScheme.primaryContainer)
                            .padding(8.dp)
                    ) {
                        Text(
                            text = letter.toString(),
                            style = MaterialTheme.typography.titleMedium,
                            color = MaterialTheme.colorScheme.onPrimaryContainer,
                            fontWeight = FontWeight.Bold
                        )
                    }
                }
                
                items(
                    items = contactsInGroup,
                    key = { it.id }
                ) { contact ->
                    ListItem(
                        headlineContent = { Text(contact.name) },
                        supportingContent = { Text(contact.phone) },
                        modifier = Modifier
                            .clickable { selectedContact = contact }
                            .padding(horizontal = 16.dp, vertical = 8.dp)
                    )
                    Divider(color = Color.LightGray, thickness = 0.5.dp)
                }
            }
        }
    }
}

// Add this to your app
@Composable
fun MyApp() {
    MaterialTheme {
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colorScheme.background
        ) {
            JetpackComposeLazyColumnDemo()
        }
    }
}

This complete example demonstrates a contacts app using Jetpack Compose LazyColumn with:

  • Grouped contacts with sticky headers
  • Quick navigation buttons
  • Item selection and detail display
  • Proper state management
  • Smooth scrolling animations

To use this code, you'll need the following dependencies in your build.gradle file:

dependencies {
    implementation "androidx.compose.ui:ui:1.5.0"
    implementation "androidx.compose.material3:material3:1.1.0"
    implementation "androidx.compose.foundation:foundation:1.5.0"
    implementation "androidx.compose.runtime:runtime:1.5.0"
    implementation "androidx.activity:activity-compose:1.7.0"
}

Example 2

import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Star import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch

@Composable fun LazyColumnExample() { // Create some sample data data class Item( val id: Int, val title: String, val description: String, var isFavorite: Boolean = false )

val items = remember {
    mutableStateListOf<Item>().apply {
        repeat(50) { index ->
            add(
                Item(
                    id = index,
                    title = "Item $index",
                    description = "This is a detailed description for item $index. " +
                            "It contains multiple lines of text to demonstrate how LazyColumn " +
                            "handles multiline content in each item.",
                    isFavorite = index % 5 == 0
                )
            )
        }
    }
}

// LazyListState to control scrolling behavior
val lazyListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()

// Track selected item
var selectedItemId by remember { mutableStateOf<Int?>(null) }

Column(modifier = Modifier.fillMaxSize()) {
    // Header section with scroll controls
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .background(MaterialTheme.colorScheme.primaryContainer)
            .padding(16.dp)
    ) {
        Text(
            text = "Jetpack Compose LazyColumn Demo",
            fontSize = 20.sp,
            fontWeight = FontWeight.Bold,
            color = MaterialTheme.colorScheme.onPrimaryContainer
        )

        Spacer(modifier = Modifier.height(8.dp))

        // Scroll control buttons
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Button(
                onClick = {
                    coroutineScope.launch {
                        // Scroll to top
                        lazyListState.animateScrollToItem(0)
                    }
                }
            ) {
                Text("Scroll to Top")
            }

            Button(
                onClick = {
                    coroutineScope.launch {
                        // Scroll to middle
                        lazyListState.animateScrollToItem(items.size / 2)
                    }
                }
            ) {
                Text("Scroll to Middle")
            }

            Button(
                onClick = {
                    coroutineScope.launch {
                        // Scroll to bottom
                        lazyListState.animateScrollToItem(items.size - 1)
                    }
                }
            ) {
                Text("Scroll to Bottom")
            }
        }
    }

    // Selected item details
    selectedItemId?.let { id ->
        val selectedItem = items.find { it.id == id }
        selectedItem?.let {
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Text(
                        text = it.title,
                        fontSize = 18.sp,
                        fontWeight = FontWeight.Bold
                    )
                    
                    Spacer(modifier = Modifier.height(8.dp))
                    
                    Text(text = it.description)
                    
                    Spacer(modifier = Modifier.height(8.dp))
                    
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.End
                    ) {
                        Button(
                            onClick = { selectedItemId = null }
                        ) {
                            Text("Close")
                        }
                    }
                }
            }
        }
    }

    // Main LazyColumn with different properties demonstrated
    LazyColumn(
        state = lazyListState,
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
        modifier = Modifier.fillMaxSize()
    ) {
        // Header item
        item {
            Text(
                text = "Header",
                modifier = Modifier
                    .fillMaxWidth()
                    .background(MaterialTheme.colorScheme.secondaryContainer)
                    .padding(16.dp),
                fontSize = 16.sp,
                fontWeight = FontWeight.Bold,
                color = MaterialTheme.colorScheme.onSecondaryContainer
            )
        }

        // Sticky header
        stickyHeader {
            Surface(
                modifier = Modifier.fillMaxWidth(),
                color = MaterialTheme.colorScheme.primary
            ) {
                Text(
                    text = "Sticky Header - Always Visible While Scrolling",
                    modifier = Modifier.padding(16.dp),
                    color = MaterialTheme.colorScheme.onPrimary,
                    fontWeight = FontWeight.Bold
                )
            }
        }

        // Items with index
        itemsIndexed(
            items = items,
            key = { _, item -> item.id } // Using key for better performance and animations
        ) { index, item ->
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .clickable { selectedItemId = item.id },
                elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
            ) {
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(16.dp)
                ) {
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween,
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        Text(
                            text = "Position $index: ${item.title}",
                            fontWeight = FontWeight.Bold,
                            fontSize = 16.sp
                        )
                        
                        IconButton(
                            onClick = {
                                items[index] = item.copy(isFavorite = !item.isFavorite)
                            }
                        ) {
                            Icon(
                                imageVector = if (item.isFavorite) Icons.Filled.Favorite else Icons.Filled.Star,
                                contentDescription = "Toggle Favorite",
                                tint = if (item.isFavorite) Color.Red else Color.Gray
                            )
                        }
                    }
                    
                    Spacer(modifier = Modifier.height(4.dp))
                    
                    Text(
                        text = item.description,
                        maxLines = 2,
                        overflow = TextOverflow.Ellipsis
                    )
                }
            }
        }

        // Footer item
        item {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = "End of List - ${items.size} items total",
                    color = Color.Gray
                )
            }
        }
    }
}

}

// For use in a standard Compose app: @Composable fun LazyColumnApp() { MaterialTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { LazyColumnExample() } } }

// Required dependencies in build.gradle: implementation "androidx.compose.ui:ui:1.5.4" implementation "androidx.compose.material3:material3:1.1.2" implementation "androidx.compose.foundation:foundation:1.5.4" implementation "androidx.compose.material:material-icons-extended:1.5.4" implementation "androidx.activity:activity-compose:1.8.1"

For the latest Jetpack Compose versions and documentation, visit the official Jetpack Compose website.

By implementing Jetpack Compose LazyColumn in your applications, you can create efficient, responsive lists with significantly less code than traditional RecyclerView implementations. The declarative nature of Jetpack Compose LazyColumn makes it easier to understand, maintain, and modify your list implementations while providing excellent performance for your Android applications.​​​​​​​​​​​​​​​​