Lazy Layouts in Jetpack Compose

Learn how to use Lazy Composables in Jetpack Compose to simply display data in your app. By Enzo Lizama.

5 (4) · 1 Review

Download materials
Save for later
Share

One of the most common things apps do is display data. From your phone contacts to your favorite artist’s songs on Spotify, you’re always viewing sorted information that’s been formatted in some way — columns, rows, grids, and more.

Different platforms do this in different ways, of course, and methods have changed over time. Today’s Android apps use Jetpack Compose with Lazy composables — a modern, easy and efficient solution to display large lists of data. Android developers have evolved from using the now-deprecated ListView to the current RecyclerView. Both methods use XML code to represent the user interface and create adapters to handle each element.

Jetpack Compose was introduced at Google I/O 2019. It completely removed the XML code and offered a much easier way to handle common features like displaying data.

In this tutorial, you’ll learn about Lazy composables in Jetpack Compose. Specifically, you’ll learn:

  • What a Lazy composable is and how it works under the hood.
  • How to work with LazyColumn, LazyRow and Lazy grids.
  • About the main benefits of using them over the non-lazy options.
Note: This tutorial assumes you know the basics about Jetpack Compose. If you’re new to Jetpack Compose, check out Jetpack Compose Tutorial for Android: Getting Started.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

You’re going to build an app that allows you to display information about cute cats in list and grid formats using Lazy composables. The sample app has a very simple project structure:

  • data.model: The Cat model that represents the structure of what you’re going to retrieve from the internet service. Cute cats!
  • data.network: The requests to external services. Retrofit is the third-party library you’re going to use during this project to make HTTP requests. It’s straightforward and useful.
  • data.repository: FeedRepository has the responsibility to make the API calls and handle the response.
  • ui.theme: In this package lives some theme configurations that come by default with the Compose starter project boilerplate. They’re not the main focus of this article, though.
  • ui.cats: This is the starting point and the main package for this tutorial. You’re going to work with composables and learn how to integrate them into the app. The CatItem represents the list item, and CatFeedScreen is the whole composable screen that displays the information for those cute cats. You can also find the CatFeedViewModel that deals with the current state of the app here.

Build and run.

You’ll see an empty screen — that’s because you haven’t put the Lazy composables in place yet.

https://koenig-media.raywenderlich.com/uploads/2022/06/1-starter-app.png

Understanding Lazy Lists

To better understand the benefits Lazy composables offer, you first have to understand what they are and how they work.

Imagine that you want to display a large amount of data with an unknown number of items. If you decide to use a Column/Row layout, this could translate into a lot of performance issues because all the items will compose whether they’re visible or not. The Lazy option lets you lay out the components when they’re visible in the widget viewport. The available list of components includes LazyColumn, LazyRowand their grid alternatives.

Lazy composables follow the same set of principles as the RecyclerView widget but remove all that annoying boilerplate code.

Understanding LazyListScope

An interesting detail about Lazy composables is that they’re slightly different from other kinds of layouts because instead of expecting a @Composable instance, they offer a DSL block from LazyListScope. DSL, or Domain Specific Language, allows the app to create specific instructions to solve a specific problem. In these scenarios, Kotlin uses type-safe builders to create a DSL that fits perfectly for building complex hierarchical data structures in a semi-declarative way. The LazyListScope plays the role of a receiver scope in LazyRow and LazyColumn.

@LazyScopeMarker
interface LazyListScope {
    // 1
    fun item(key: Any? = null, content: @Composable LazyItemScope.() -> Unit)

    // 2
    fun items(
        count: Int,
        key: ((index: Int) -> Any)? = null,
        itemContent: @Composable LazyItemScope.(index: Int) -> Unit
    )
		
    // 3
    @ExperimentalFoundationApi
    fun stickyHeader(key: Any? = null, content: @Composable LazyItemScope.() -> Unit)
}

Here’s what’s happening in the code above:

  1. The item receiver allows adding a single composable item into the Lazy layout. You can add as many item receivers as you like, but if you want to add many, check the items option below.
  2. The items receiver expects a count of items instead of defining the content of every item individually. Here, you define the length of the list and create the specifications for every item.
  3. Finally, the stickyHeader adds a sticky item at the top. It will remain pinned even when scrolling. The header will remain pinned until the next header takes its place. This is very useful for sub-list scenarios like contact apps or movie categories.

In the next section, you’re going to practice what you’ve learned about Lazy composables and learn how to implement them in the app you’re building.

Adding Lazy Composables in Your App

Now it’s time for the fun part: coding.

Note: This tutorial uses version 1.1.1 of Jetpack Compose.

First, be sure you already have the right dependencies for this tutorial. Open the build.gradle file for the module, and notice it has the following dependencies:

def compose_version = "1.1.1"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"

Adding a LazyRow

Now that you have confirmed the dependencies, open CatItem.kt and look for the // TODO : Add list of tags comment. Replace it with this:

LazyRow(
    modifier = Modifier.align(Alignment.CenterHorizontally)
) {
  items(cat.tags) {
    CatTag(tag = it)
  }
}

The code above takes advantage of LazyRow to display the tags of a cat item in a horizontal scrollable view using the CatTag composable function.

If you’re a very detailed reader, you’ll notice the items method you’re implementing is slightly different from the previous one. This one expects a List of items instead of the count. But how is this possible?

Just remember that Kotlin offers extension functions to make your life easier. Command-click or Control-click the method, and you’ll be redirected to the implementation. The code is shown below:

inline fun <T> LazyListScope.items(
    items: List<T>,
    noinline key: ((item: T) -> Any)? = null,
    crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit
) = items(items.size, if (key != null) { index: Int -> key(items[index]) } else null) {
    itemContent(items[it])
}

Notice how the method passes the item’s size to LazyListScope.items and also deals with the keys for the scenario when they’re not null.