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.​​​​​​​​​​​​​​​​