LazyRow in Jetpack Compose

Jetpack Compose, Android’s modern UI toolkit, simplifies UI development with its declarative approach. One of the essential composables for creating scrollable content is the LazyRow. It is specifically designed to efficiently display a large number of items in a horizontal list. Understanding how to use LazyRow is crucial for building performant and user-friendly UIs.

What is LazyRow in Jetpack Compose?

LazyRow is a composable function in Jetpack Compose that displays a horizontally scrolling list of items. It is “lazy” because it only composes and lays out the items that are currently visible on the screen (or about to become visible). This lazy behavior is highly efficient when dealing with large datasets, as it minimizes resource usage and improves performance compared to traditional scrolling containers that render all items at once.

Why Use LazyRow?

  • Performance: Only visible items are composed and laid out, significantly improving performance for large datasets.
  • Memory Efficiency: Reduces memory footprint as only a subset of items are kept in memory.
  • Simplicity: Easy to implement and integrate into your Compose UI.

How to Implement LazyRow

Step 1: Add Dependencies

Make sure you have the necessary dependencies in your build.gradle file:

dependencies {
    implementation("androidx.compose.ui:ui:1.6.0")
    implementation("androidx.compose.material:material:1.6.0")
    implementation("androidx.compose.foundation:foundation:1.6.0") // Required for LazyRow
    implementation("androidx.compose.runtime:runtime:1.6.0")
    implementation("androidx.activity:activity-compose:1.8.2") // Required for Compose interoperability with Activities
}

Replace 1.6.0 (or 1.8.2 for activity-compose) with the latest stable version as necessary.

Step 2: Basic Implementation

A simple implementation of LazyRow to display a list of text items:


import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun SimpleLazyRow() {
    val items = List(20) { "Item $it" }

    LazyRow(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(items) { item ->
            Card {
                Text(
                    text = item,
                    modifier = Modifier.padding(16.dp)
                )
            }
        }
    }
}

Explanation:

  • A list of 20 items is created.
  • LazyRow is used to display the items horizontally.
  • contentPadding adds padding around the row.
  • horizontalArrangement specifies spacing between items.
  • items(items) iterates through the list and renders each item.

Step 3: Custom Item Content

You can customize the item content within the LazyRow. For instance, display images with text:


import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp

data class ItemData(val id: Int, val text: String, val imageResId: Int)

@Composable
fun CustomItemLazyRow() {
    val items = listOf(
        ItemData(1, "Item 1", android.R.drawable.ic_menu_camera),
        ItemData(2, "Item 2", android.R.drawable.ic_menu_gallery),
        ItemData(3, "Item 3", android.R.drawable.ic_menu_manage)
    )

    LazyRow(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(items) { item ->
            Card {
                Column(
                    modifier = Modifier.padding(8.dp),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Image(
                        painter = painterResource(id = item.imageResId),
                        contentDescription = null, // Provide a proper content description
                        modifier = Modifier.size(48.dp)
                    )
                    Spacer(modifier = Modifier.height(4.dp))
                    Text(text = item.text)
                }
            }
        }
    }
}

Key improvements and explanations:

  • A data class ItemData is introduced to hold the item’s ID, text, and image resource ID.
  • An Image composable is used to display the images. Note the use of `android.R.drawable.ic_menu_camera`, etc as examples, remember to use appropriate drawables from your own resources. Add proper content descriptions to the Image.
  • Items are displayed in a Column for vertical arrangement within each card.

Step 4: Handling Click Events

Add click event handling to the items within the LazyRow:


import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview

data class ItemData(val id: Int, val text: String, val imageResId: Int)

@Composable
fun ClickableItemLazyRow(onItemClick: (ItemData) -> Unit) {
    val items = listOf(
        ItemData(1, "Item 1", android.R.drawable.ic_menu_camera),
        ItemData(2, "Item 2", android.R.drawable.ic_menu_gallery),
        ItemData(3, "Item 3", android.R.drawable.ic_menu_manage)
    )

    LazyRow(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(items) { item ->
            Card(
                modifier = Modifier.clickable { onItemClick(item) }
            ) {
                Column(
                    modifier = Modifier.padding(8.dp),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Image(
                        painter = painterResource(id = item.imageResId),
                        contentDescription = null, // Always add proper content descriptions
                        modifier = Modifier.size(48.dp)
                    )
                    Spacer(modifier = Modifier.height(4.dp))
                    Text(text = item.text)
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun ClickableItemLazyRowPreview() {
    ClickableItemLazyRow(onItemClick = { item ->
        println("Clicked item: ${item.text}")
    })
}

In this example:

  • The clickable modifier is used to make each card clickable.
  • An onItemClick lambda function is passed as a parameter, allowing the caller to handle the click event.
  • When an item is clicked, the onItemClick function is invoked with the corresponding ItemData.

Step 5: Adding a Key to Items

When dealing with dynamic lists where items may be added, removed, or reordered, providing a key to each item in the LazyRow can greatly improve performance. The key should be a unique identifier for each item that remains consistent across recompositions. Use the `key` parameter inside the `items` block.


import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview

data class ItemData(val id: Int, val text: String)

@Composable
fun KeyedLazyRow() {
    val items = listOf(
        ItemData(1, "Item 1"),
        ItemData(2, "Item 2"),
        ItemData(3, "Item 3")
    )

    LazyRow(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(
            items = items,
            key = { item -> item.id }
        ) { item ->
            Card {
                Text(
                    text = item.text,
                    modifier = Modifier.padding(16.dp)
                )
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun KeyedLazyRowPreview() {
    KeyedLazyRow()
}

In this example, each ItemData has an id field, which is used as the key in the items block:

  • Using the key parameter optimizes the recomposition process by helping Compose identify which items have changed.
  • Without a key, Compose may have to recompose all items whenever the list changes, leading to performance issues.

Conclusion

LazyRow is a powerful and efficient way to display horizontal lists in Jetpack Compose. By leveraging its lazy composition, you can create performant UIs that handle large datasets with ease. Incorporating customizations such as custom item layouts, click handling, and item keys further enhances the flexibility and efficiency of your Compose applications. Whether displaying simple text lists or complex UI elements, LazyRow is an indispensable tool for modern Android UI development.