Earlier this year we open-sourced Litho, a declarative Android framework for efficient UI rendering. Litho delivers best-in-class UI performance through a simple, declarative API.
Litho is primarily a rendering API that uses Components for displaying pieces of a UI on screen. But efficient UI rendering is only part of the challenge that we have to deal with at Facebook. Like many mobile apps, Facebook centers on a scrollable surface that fetches and displays lists of data. When you build a list with RecyclerView you have to think about how to maintain the adapters in sync with your data and notify the adapters of any changes. This typically requires a lot of manual index handling and results in stateful, imperative code that is difficult to maintain and reason about as your product grows. In addition, RecyclerView adapters are difficult to compose, and integrating multiple data sources into the same surface is not trivial.
We address these challenges with Sections, a new declarative and composable way to write highly-optimized list surfaces, built on top of Litho. Today, we’re excited to open-source the Sections API.
Sections are a way of structuring data and translating it into Litho Components. If you visualize your surface 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 uses the same declarative data model as Litho and transparently handles 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.
For easy integration with Litho, Sections provides a built-in Component, called RecyclerCollectionComponent, that can render a hierarchy of Sections. The Sections hierarchy becomes a data source for the RecyclerCollectionComponent, and the Components that render your data will become items in the RecyclerView adapter under the hood. All the complexity of handling operations on your adapter, such as inserts or removes, is hidden away and handled by the infrastructure.
Let’s explore the Sections API and how to reason about your product when working with Sections through a simple example: a scrollable list of artists grouped by decade.
In the example above, we wrote a class annotated with
GroupSectionSpec, which is a way of declaring that the generated class will be a Section that can have other Sections as children.
Let’s look at how we’re using the
DataDiffSection in our example: we’re passing it the list of artists we want to display through the data prop and we’re using an event handler to declare that we want those items to be rendered on screen using an ArtistItemCard component. That’s all the developer needs to think about when creating a list: what the data is and how the items should be rendered; the framework handles optimizations and translates the data model into the UI.
DataDiffSection are building blocks in the Sections API and can calculate a set of changes that need to be applied on the current Sections hierarchy so that it represents the updated data.
SingleComponentSection can be used for creating and diffing a list that contains a single Component item. In our example, we’re using it to create a sticky header for each decade.
DataDiffSection can be used for lists of homogeneous data model objects. The framework implementation of the
DataDiffSection uses the Android DiffUtil class for determining the minimal set of operations that is needed to update the UI given the current and new lists of data.
We can render a Sections hierarchy with Litho by passing it to a
Not just an API: Data diffing
Let’s imagine that your data-fetching handler receives a server update with a new list of data and only one item in that list changed since the last time it was rendered.
If you were using RecyclerView, the naive solution for updating the UI would be to call
notifyDataSetChanged() on your adapter, which would recreate and rebind all the visible views. This can have a big impact on performance, especially if it happens often. To make this efficient, you’d have to do granular updates or implement a data diffing mechanism for each adapter, which is not scalable and makes the code more error-prone and difficult to maintain.
Since only the props for one of the leaf Components changed, the Sections framework will detect that it only needs to recreate and update the layout for that Component. The framework will skip recreating the portion of the tree that doesn’t change, such as the subtree for the header, saving a lot of time on layout and render operations.
When using Sections, it’s easy to aggregate different data sources and render them in the same list. You can fetch your data from any number of sources (server, local storage database, etc.) and compose it into the same Section hierarchy. If each Section is powered by its own data source, any data changes affect only the subtree that is using that source.
At Facebook, we converted some highly used scrollable surfaces from a traditional RecyclerView and Views implementation to Sections and Litho and we saw dramatic performance improvements. Converting the Comments surface in Facebook for Android to Sections resulted in a 42% scroll performance improvement.
Support for View rendering
Sections work best when combined with the rendering optimizations that Litho Components offer. However, the API also provides support for rendering with Views. We learned that this makes it easier to transition to a Sections architecture and still take advantage of its performance benefits whether your product’s UI uses traditional Views, Litho Components, or a hybrid.
Sections is a Litho extension that rounds out our vision of a declarative API for building maintainable, highly-performant list surfaces with simpler code. The Sections API adopts the same declarative programming model as Litho and provides an easy way of translating data sources into lists of Components that are rendered on screen. The programming paradigm enforced by Sections makes developing and maintaining list surfaces a lot easier and more scalable, while the performance optimizations that come from automatic changeset calculations and minimal UI updates are seen in all surfaces that use the framework.
Find more about how you can start using Sections here.