Skip to main content

Basics: DiffSection and GroupSection

The Sections API is a declarative, composable, and thread-safe API for writing highly-optimized list screens, built on top of Litho. It tries to address issues we've had at Facebook when writing complex lists, such as maintaining many view types, handling multiple data sources and composing lists together.

While Litho Components are used for displaying pieces of UI, Sections are a way of composing data into a list of Litho Components or Views. If you visualize your screen 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.

Sections use the same hierarchical declarative data model as Components and under the hood transparently handle things like calculating minimal sets of changes for data updates and doing 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. It is built on top of Android's RecyclerView.

In this document we'll walk you through the parts necessary to build a list: a RecyclerCollectionComponent for your layout, which sets several DiffSection that are either SingleComponentSection or DataDiffSection, all contained inside GroupSection hierarchies.

If you have the time we recommend you also watch this Litho Lesson which covers the basics of how the diffing happens:

RecyclerCollectionComponent#

You can use RecyclerCollectionComponent as you would use any other component in the framework by building it and adding it as a child in your layout.

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

This is all you need to know about adding Sections to your layout for now. You can find an explanation of advanced use cases such as snapping and horizontal lists on the RecyclerCollectionComponent docs.

DiffSection#

Most lists are composed of groups of homogeneous items interleaved with one-off items. As an example, imagine 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, you can use this Section 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 you have to pass 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))

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 will check whether the model we have received in the new list of data changed since the last time we rendered it.

If the data changed for the item in that position, the framework will dispatch a RenderEvent for that item and the DataDiffSection will use the RenderEvent handler we passed as 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

You can visit the Best Practices and Performance documentation for a deep dive of how to build performant and accurate diffing, including diffing for classes where equals is not overwritten.

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: you only need to write one method annotated with @OnCreateChildren. This method returns a tree of Sections that will have root in this GroupSectionSpec.

Let's have a look at how you would 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 of the pieces together#

Imagine a surface that has multiple such subsections consisting of a header and list of Strings. An example could be a list of contacts grouped alphabetically, delimited by headers showing the first letter of the name. You can easily achieve that by creating a GroupSectionSpec that has a SimpleListSection child for every letter.

@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();
}
}

Below is a representation of the tree of Sections that has the 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 responsibilities are to describe what data it needs and how that data should be rendered.

The Sections hierarchy becomes a data source for the RecyclerCollectionComponent, and we only have to pass the data that the ContactsSection will use to create each SimpleListSection.

@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();
}

That is everything there is to most list you'll build. A small API surface for a lot of functionality! The complexity of handling operations on your list, such as inserts or removes, is hidden away and handled by the infrastructure.

Where to go next?#

We suggest reading into Best Practices and Performance first. Afterwards you can head for one of the documents referring to advanced use cases your list may have, such as complex list layouts, granular dependency injection, or prefetch and pagination.