LazyRow in Jetpack Compose: Building Efficient Horizontal Lists

Jetpack Compose, Google’s modern UI toolkit for building native Android apps, provides a declarative way to create user interfaces. Among its powerful features, LazyRow stands out as an efficient and flexible way to display horizontal lists. This article explores the ins and outs of LazyRow, demonstrating how to use it effectively, optimize its performance, and create stunning horizontal list layouts in your Android applications.

What is LazyRow in Jetpack Compose?

LazyRow is a composable function in Jetpack Compose that displays a horizontally scrolling list. It is analogous to RecyclerView in the traditional Android View system but with a more concise and declarative API. The term ‘lazy’ refers to the fact that LazyRow only composes and renders the items that are currently visible on the screen. This behavior significantly enhances performance, especially when dealing with large datasets.

Why Use LazyRow?

  • Performance: Only visible items are composed and rendered.
  • Efficiency: Handles large datasets gracefully by avoiding unnecessary computations.
  • Flexibility: Highly customizable to suit various UI requirements.
  • Simplicity: Offers a more straightforward API compared to traditional RecyclerView.

Basic Implementation of LazyRow

To implement LazyRow, you need to follow a few key steps.

Step 1: Add Dependency

Ensure that your build.gradle file contains the necessary dependencies for Jetpack Compose. Typically, you’ll need the Compose UI and Foundation dependencies:

dependencies {
    implementation("androidx.compose.ui:ui:1.6.0") // Or the latest version
    implementation("androidx.compose.foundation:foundation:1.6.0") // Or the latest version
    implementation("androidx.compose.material:material:1.6.0") // Optional, but often useful
    implementation("androidx.compose.runtime:runtime:1.6.0")
    implementation("androidx.compose.ui:ui-tooling-preview:1.6.0")
    debugImplementation("androidx.compose.ui:ui-tooling:1.6.0")
}

Step 2: Implement LazyRow Composable

Here’s a basic example of how to create a LazyRow:


import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.Alignment

@Composable
fun SimpleLazyRowExample() {
    val items = List(100) { "Item $it" }

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

@Preview(showBackground = true)
@Composable
fun PreviewSimpleLazyRowExample() {
    SimpleLazyRowExample()
}

In this example:

  • LazyRow is used to create a horizontally scrolling list.
  • items is a list of strings that will be displayed as items in the list.
  • Modifier.fillMaxSize() makes the LazyRow take up the maximum available space.
  • contentPadding adds padding around the entire list.
  • horizontalArrangement adds spacing between the items.
  • The items(items) block iterates over the list of items, creating a composable for each item.

Customizing LazyRow

LazyRow can be customized extensively to fit your UI needs.

Adding Spacing

Use Arrangement.spacedBy() to add spacing between the items:


LazyRow(
    horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
    // Items
}

Adding Padding

Use PaddingValues to add padding around the entire list:


LazyRow(
    contentPadding = PaddingValues(16.dp)
) {
    // Items
}

Item Customization

Customize the appearance of each item using standard Compose modifiers:


items(items) { item ->
    Card(
        modifier = Modifier
            .padding(4.dp)
            .width(200.dp)
    ) {
        Text(
            text = item,
            modifier = Modifier.padding(8.dp)
        )
    }
}

Handling Clicks in LazyRow

To make items in LazyRow clickable, you can use the clickable modifier.


import androidx.compose.foundation.clickable
import androidx.compose.material.SnackbarData
import androidx.compose.material.SnackbarDuration
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.remember
import kotlinx.coroutines.launch
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.material3.*

@Composable
fun ClickableLazyRowExample() {
    val items = List(10) { "Item $it" }
    val snackbarHostState = remember { SnackbarHostState() }
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        }
    ) { innerPadding ->
        LazyRow(
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding),
            contentPadding = PaddingValues(16.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            items(items) { item ->
                Card(
                    modifier = Modifier
                        .padding(4.dp)
                        .clickable {
                            scope.launch {
                                val result = snackbarHostState.showSnackbar(
                                    message = "Clicked: $item",
                                    actionLabel = "Dismiss",
                                    duration = SnackbarDuration.Short
                                )
                                when (result) {
                                    SnackbarResult.ActionPerformed -> {
                                        println("Snackbar action performed")
                                    }

                                    SnackbarResult.Dismissed -> {
                                        println("Snackbar dismissed")
                                    }
                                }
                            }
                        }
                ) {
                    Text(
                        text = item,
                        modifier = Modifier.padding(8.dp)
                    )
                }
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun PreviewClickableLazyRowExample() {
    ClickableLazyRowExample()
}

In this enhanced example:

  • We use clickable { ... } modifier on the Card to make each item clickable.
  • Inside the clickable block, we launch a coroutine to show a Snackbar when an item is clicked.
  • The Snackbar displays the item that was clicked.
  • Added Scaffold composable and snackbarHost parameter to properly display a Snackbar on the screen.

Advanced Usage: Dynamic Content and Keys

For lists that change over time, using keys can significantly improve performance. Keys help Compose identify which items have changed, moved, or been added/removed.


import androidx.compose.runtime.*
import androidx.compose.foundation.lazy.*
import androidx.compose.material3.*
import androidx.compose.ui.*
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.*

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

@Composable
fun DynamicLazyRowExample() {
    var items by remember {
        mutableStateOf(
            List(5) { ListItem(it, "Item $it") }
        )
    }

    Column {
        Button(onClick = {
            val newId = items.size
            items = items + ListItem(newId, "Item $newId")
        }) {
            Text("Add Item")
        }

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

@Preview(showBackground = true)
@Composable
fun PreviewDynamicLazyRowExample() {
    DynamicLazyRowExample()
}

In this dynamic example:

  • A data class ListItem is used to hold unique IDs and text.
  • The items list is mutable and can be updated using a button click.
  • The key parameter in the items block is used to provide a stable and unique key for each item, which helps Compose optimize recompositions when the list changes.

Best Practices for Using LazyRow

  • Use Keys: When dealing with dynamic lists, always use keys to optimize recompositions.
  • Manage Content Padding: Use contentPadding to provide consistent spacing around the list.
  • Optimize Item Layouts: Keep item layouts simple and efficient to avoid performance bottlenecks.
  • Handle Clicks Carefully: Use the clickable modifier to handle clicks, and consider using a rememberCoroutineScope for launching coroutines within the click handler.

Conclusion

LazyRow in Jetpack Compose is a versatile and performant way to display horizontal lists. By understanding its core features and customization options, you can create stunning and efficient UIs in your Android applications. Whether you are displaying a small set of items or a large dataset, LazyRow provides the tools you need to build responsive and visually appealing horizontal lists.