Skip to main content

Sections Basics

caution

This page covers the older Java codegen-based Sections API. If creating a new list in Kotlin, refer to the Lazy Collection docs.

Introduction​

The Sections API provides a declarative, composable, and thread-safe API for writing highly optimised List screens. It addresses issues experienced when writing complex Lists, such as maintaining many view types, handling multiple data sources, and composing Lists together.

Litho Components are used for various aspects of the UI. Sections are a way of composing data into a list of Litho Components or Views.

If the screen is visualized as being a tree of components, the nodes for the root of the tree and the subtrees are Sections, while the leaves are Litho Components that represent individual items that will be displayed on screen. The following diagram shows the relationship between Sections and Litho components.

Sections use the same hierarchical declarative data model as Components. Under the hood, Sections transparently handle processes such as calculating minimal sets of changes for data updates and granular UI refreshes. As part of Litho, the Sections API shares the same main concepts, such as annotation-based code generation, event handling, props, and state updates. Sections functionality is built on top of Android's RecyclerView.

List building blocks​

This section details the parts necessary to build a list: a RecyclerCollectionComponent for layout, which sets several DiffSections that are either SingleComponentSection or DataDiffSection, all contained inside GroupSection hierarchies.

As preparation, the following 10-minute video 'Litho Lessons: Diffing in Sections' covers the basics of how diffing operates.

RecyclerCollectionComponent​

As with any other component, the RecyclerCollectionComponent can be used by building it and adding it as a child in the layout:

RecyclerCollectionComponent.create(c)
.section(createGroupSection())
.build();

For now, this is all that needs to be known about adding Sections to a layout. For advanced use cases, such as snapping and horizontal Lists, see the RecyclerCollectionComponent page.

DiffSection​

Most Lists are composed of groups of homogeneous items interleaved with one-off items: picture a list of contacts sorted alphabetically and separated by headers indicating the first letter of the contact's name.

Following this model, the Sections API ships two DiffSectionSpec implementations that can be combined to represent the structure of nearly any surface: SingleComponentSection and DataDiffSection.

SingleComponentSection​

A SingleComponentSection is used to represent a one-off row in a complex list. As the name suggests, Section can be used to render a single Component, which is passed to this Section as its only prop.

One of the typical use cases of a SingleComponentSection is to add a loading spinner at the end of a list:

SingleComponentSection.create(c)
.component(Progress.create(c).build())
.build();

Or a header for your data:

SingleComponentSection.create(c)
.component(Text.create(c).text("Friends").build())
.build();

DataDiffSection​

A DataDiffSection is used to represent a homogeneous list of immutable data. The minimal information that must be passed to a DataDiffSection is the list of items that it needs to render and a callback for rendering each item in this list.

DataDiffSection.<MyModel>create(c)
.data(ImmutableList.of(new MyModel(1), new MyModel(2), new MyModel(3)))
.renderEventHandler(MyGroupSection.onRenderEdge(c));

The DataDiffSection is designed to efficiently render the parts of a surface that handle large flows of data. When an item at a certain position needs to be displayed on screen, the framework checks whether the model received in the new list of data changed since the last time it was rendered.

If the data changed for the item in that position, the framework would dispatch a RenderEvent for that item, and the DataDiffSection will use the RenderEvent handler passed as a prop to create a Component for that item and display it.

@OnEvent(RenderEvent.class)
static RenderInfo onRenderEdge(
SectionContext c,
@FromEvent MyModel model) {
return ComponentRenderInfo.create()
.component(MyModelItemComponent.create(c).item(model).build())
.build();
}

By default, DataDiffSection will detect data changes by checking instance equality and subsequently calling equals on the objects in the data list.

tip

For a deep dive into how to build performant and accurate diffing, including diffing for classes where equals is not overridden, see the Best Practices and Performance documentation.

GroupSection​

A GroupSectionSpec is used to structure your Sections into a hierarchy, each one responsible for rendering its own chunk of data.

Group section specs are classes annotated with @GroupSectionSpec. Implementing a GroupSectionSpec is very simple: just write one method annotated with @OnCreateChildren. This method returns a tree of Sections that will have root in this GroupSectionSpec.

The following code shows how to declare a simple List that contains a header followed by a list of String items:

@GroupSectionSpec
class SimpleListSectionSpec {

@OnCreateChildren
static Children onCreateChildren(
SectionContext c,
@Prop String headerTitle,
@Prop List<String> data) {
return Children.create()
.child(
SingleComponentSection.create(c)
.component(
Text.create(c)
.text(headerTitle)
.build()))
.child(
DataDiffSection.<String>create(c)
.data(data)
.renderEventHandler(SimpleListSection.onRender(c)))
.build();
}

@OnEvent(RenderEvent.class)
static RenderInfo onRender(
SectionContext c,
@FromEvent String model) {
return ComponentRenderInfo.create()
.component(
Text.create(c)
.text(model)
.build())
.build();
}
}

Putting all the pieces together​

Imagine a surface that has multiple sub-sections consisting of a header and a list of Strings. An example of this is a list of contacts grouped alphabetically, delimited by headers showing the first letter of the name. This could be achieved easily by creating a GroupSectionSpec that has a SimpleListSection child for every letter, as described in the following code:

@GroupSectionSpec
class ContactsSectionSpec {

@OnCreateChildren
static Children onCreateChildren(
SectionContext c,
@Prop List<String> data) {

final Children.Builder builder = Children.create();

for(char firstLetter = 'A'; firstLetter <= 'Z'; firstLetter++) {
builder.child(
SimpleListSection.create(c)
.key(String.valueOf(firstLetter))
.headerTitle(String.valueOf(firstLetter))
.data(getItemsForLetter(firstLetter, data)));
}

return build.build();
}
}

The following image is a representation of the tree of Sections that has its root in ContactsSectionSpec. Each node in the tree is a Section, and the leaves are Components that can be rendered on screen. Each one of the sections in the tree acts as a data source. Its purpose is to describe what data it needs and how that data should be rendered.

The Sections hierarchy becomes a data source for the RecyclerCollectionComponent. To create each SimpleListSection, it's only necessary to pass the data that the ContactsSection will use, as shown in the following code.

@OnCreateLayout
static Component onCreateLayout(
final ComponentContext c) {
return RecyclerCollectionComponent.create(c)
.section(
ContactsSection.create(new SectionContext(c))
.dataModel(ImmutableList.of("Andy", "Aziz", "Aditya", "Nico", "Sergey"))
.build())
.build();
}
note

The complexity of handling operations on a list, such as inserts or removes, is hidden away and handled by the infrastructure.

Further reading​