Sections Basics
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 DiffSection
s 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.
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();
}
The complexity of handling operations on a list, such as inserts or removes, is hidden away and handled by the infrastructure.