This document gives an insight into the implementation of Sections, for those hackers and code explorers who want to take a peek behind the scenes for inspiration or to contribute. The understanding of the implementation details described in this document are not necessary for just using Sections.
At its core, the Sections framework is responsible for producing a ChangeSet from immutable props and a hierarchy of Sections. The framework produces these
ChangeSets by creating a new section hierarchy whenever a
SectionTree is set with a Section with new props or whenever a Section in the hierarchy updates it's internal state and comparing the new hierarchy with the old hierarchy.
Using the Sections framework begins with creating a SectionTree.
SectionTree instances are responsible for:
- Computing/Recomputing changes whenever state & props values change.
- Communicate with a Target implementation that can update UI (including telling the
Targetabout new changes).
SectionTrees must be created with a
Target implementation. The Target interface is the API between
SectionTree and UI. After computing a ChangeSet from a section hierarchy, a
SectionTree instance will relay the changes to it's
Target. You can create a target for whatever custom UI you want but the Sections framework has already implemented some
Targets for you. SectionBinderTarget is one a
Target implementation that relays changes to a
RecyclerBinder for rendering.
The framework can perform incremental and conditional updates on the structure of Sections whenever any props or state values change. The infrastructure also calculates the minimal operations it needs to perform on the existing hierarchy to update the list to reflect the new data.
To update a section tree to reflect new props, create a section with the new prop values and call SectionTree#setRoot(). This is also how you set an initial root section on a tree since it's essentially diffing a new section hierarchy with an empty hierarchy.
To update a section tree when a state value changes, just perform a regular state update as described in the documentation for litho State.
You may notice that the
updateState() methods also have "async" implementations (
*async() methods will ensure that the resulting ChangeSet calculation is performed on a background thread. Otherwise the resulting ChangeSet calculation will be done synchronously on whatever thread
updateState() was called. This is just like Litho's asychronous layout.
SectionTree instances compute changes in two steps: generating trees based on props/state values, then creating a changeset by comparing two trees.
A tree is generated from a single root section by recursively calling
@OnCreateChildren on group section specs until it reaches the leaf sections, diff section specs. As it visits a new section,
- Create a new
SectionContextscoped to this new section
- Check if there's a corresponding section in the current hierarchy (via key) and transfer any state and service values over to the new section.
- Check if there's any pending state updates for the new section (via key) and perform the updates if they exist.
- Create the new child sections by calling
SectionLifecycle#createChildrenand recursively visit those child sections.
After generating a new tree,
SectionTree will recursively traverse the new tree and compare it against the current tree to generate a
ChangeSet. This is where we call
SectionLifecycle#generateChangeSet on Diff Sections. When traversing the new tree, the framework translates local indexes to global indexes as it merges all
ChangeSets into a single
ChangeSet for the whole hierarchy.
NOTE: SectionContext is an object that is used to associate each
Section instance in a hierarchy with it's
SectionContext instances are released and recreated every time a
SectionTree re-calculates it's changeset (anytime props or state change). This means you should not rely on the
SectionContext passed into your spec delegate methods to always be associated with a valid
Section instance. As a general rule, a
SectionContext object is only valid between the
@OnUnbindService methods. You should not keep an instance of
SectionContext alive outside this window.
RecyclerCollectionComponent is a Litho component that creates and binds a
SectionTree to a
Recycler behind the scenes to make it incredibly easy to use the Sections framework with Litho.
RecyclerCollectionComponent creates and holds onto a
SectionTree instance as state and exposes a prop to accept new sections. Updating the SectionTree when using RecyclerCollectionComponent is as simple as updating the section prop passed into it.