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 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.
Demo of an assortment of content types with varying sticky headers.

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 a processIntent function to handle user interactions within the item itself.
  • BuildHeaderItem adds the ability for drawing sticky headers and also supports user interactions through a processIntent function similar to BuildItem.
  • 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.

Notice how the cat tile, without a header, pushes the penguin header out.

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.

Intents from items in this demo are shown as Toast Messages.

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.

Source link