Mastering Lazy Lists in Jetpack Compose with Data Classes and MVI | by Clinton Teegarden | Dec, 2023
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.
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.
Fortunately, Lazy Lists and their features offer a more straightforward approach when compared to
RecyclerViewsand 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
genericListabstraction 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
GenericLazyItem base class establishes a pattern for building Lazy List items in a uniform manner, promoting reuse throughout your app.
BuildItemis the core function for composing the item/state’s view. It receives a
processIntentfunction to handle user interactions within the item itself.
BuildHeaderItemadds the ability for drawing sticky headers and also supports user interactions through a
processIntentfunction similar to
sectionMatcherdetermines 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.
itemKeyprovides 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 is an extension function on the
LazyListScope and aims to abstract away the repetitive logic used to render a Lazy List of
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.
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.
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.
- 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.
- Mobile App Development (625)