Mastering Lazy Lists in Jetpack Compose with Data Classes and MVI
This post explores how to structure and utilize data classes to build clean and efficient Lazy List composables within an MVI (Model-View-Intent) pattern.

Introduction
Lazy Lists in Jetpack Compose offer a powerful tool for displaying large datasets efficiently. They resemble RecyclerViews
in the legacy Android view system, but with the added benefit of Compose’s declarative UI and state management. This post delves into leveraging data classes to represent the state of items within a Lazy List while promoting reusable components across screens.
If you would like to skip to the full codebase, you can find that here.
Lazy Column Example
Through out this post, we will be building a list of items that are based on multiple content types from a hypothetical Content Management System (CMS). Below is a simple example of a LazyColumn
with a single CMS driven content type.
This sample assumes all content from the CMS utilizes the ImageOnly
composable. However, what happens when your CMS evolves, introducing diverse content types?
As content types grow, so does your when
block, making code less manageable and harder to reuse across screens. The issue compounds when incorporating items with sources not from your CMS.
Generic Lazy List
Fortunately, Lazy Lists and their features offer a more straightforward approach when compared to RecyclerViews
and are pretty well documented. However, managing multiple lists across screens can lead to repetitive code. The Generic Lazy List pattern promotes reuse and minimizes boilerplate code:
This code achieves the following:
- Demonstrates the
genericList
abstraction of item composing. - Allows the same item composables to be used across various screens.
- Manages the use of sticky headers.
- Implements keys and contentTypes for optimal list performance.

GenericLazyItem Base Class
The GenericLazyItem
base class establishes a pattern for building Lazy List items in a uniform manner, promoting reuse throughout your app.
BuildItem
is the core function for composing the item/state's view. It receives aprocessIntent
function to handle user interactions within the item itself.BuildHeaderItem
adds the ability for drawing sticky headers and also supports user interactions through aprocessIntent
function similar toBuildItem
.sectionMatcher
determines when a new section starts/ends, enabling sticky headers even when items within a section are of different types — as demonstrated in the demo codebase.itemKey
provides the item a unique identifier for efficient recomposition. When using a Lazy List without the use of keys, if a new element is added or removed from your list any item who’s index is affected by that change will be recomposed. When using keys, we avoid this and only recompose if the item actually changes.
Below is a sample of what a Data Class extending GenericLazyItem
would look like. As you can see in the below demo, the TextImageLeft
provides a state representation of how the view should composed.
GenericList Function
The genericList
function is an extension function on the LazyListScope
and aims to abstract away the repetitive logic used to render a Lazy List of GenericLazyItems
.
The genericList
function shown above performs two key tasks.
stickyHeader
rendering is based on the section matcher provided. Note that at the time of this post, the stickyHeader
function is still experimental and may change. Just like you would expect with sticky headers, as a new section header scrolls up it will push out the previous one and stick to the top of the list until a new header/section replaces it.
In this implementation and as demoed below, the genericList
supports some items having headers and some who do not wish to have headers — all within the same list. This can be seen below with the Cat image (no header) pushing out the Penguin header.

item
rendering is the next task to take place in the genericList function. This calls the BuildItem Composable of the GenericLazyItem being processed. It also takes care to provide the contentType and the key for the GenericLazyItem to ensure your Lazy List is as performant as possible.
The use of keys helps ensure that your item is not recomposed simply because it has moved positions in the list.
The use of contentType helps promote efficiency as other compositions of the same type may reuse this one more effectively.
Bonus: Intents
Intents
represent user interactions that the ViewModel needs to process. They allow for decoupling items from specific screens/ViewModels. You see these represented in both the genericList
function and the GenericLazyItem
class. In the sample, this is shown through the use of Toast Messages that are displayed based on user interactions with the list.

Conclusion
By leveraging data classes and base classes, we can build dynamic and flexible Lazy Lists in Compose. This promotes code reuse and enables efficiently displaying content from many different sources.
Further Exploration:
- The full sample code base is available here.
- Explore the Toast-based user interaction example demonstrating the intent pattern for items and an overall MVI pattern.