Sections Tutorial
This part builds on the work you did in the litho tutorial. Make sure you've read through that tutorial before returning to this one.
Recall in the litho tutorial that we left off with a vertically scrolling list with alternating background colors. In this optional part of this tutorial, we will add an horizontal scrolling unit at the top of the list by modifying ListSectionSpec
to leverage the composability of the Litho and Sections API.
1. Refactoring ListSectionSpecβ
Let us revisit ListSectionSpec. Each SingleComponentSection
inside the for loop renders almost the same component, only the number changes. In other words, each of our ListItem
components are backed by a model object of the same type: int
.
Litho provides another core section DataDiffSection
that is specifically used to render components backed by a list of models. In this step we will refactor ListSectionSpec
to use DataDiffSection
.
First, lets generate our model objects. For the sake of this example, we will just add a static method inside ListSectionSpec
to generate our model objects. In a real scenario this would be replaced with proper data fetching logic:
private static List<Integer> generateData(int count) {
final List<Integer> data = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
data.add(i);
}
return data;
}
Next, write a method that creates a ListItem
component given a model object:
@OnEvent(RenderEvent.class)
static RenderInfo onRender(final SectionContext c, @FromEvent Integer model) {
return ComponentRenderInfo.create()
.component(
ListItem.create(c)
.color(model % 2 == 0 ? Color.WHITE : Color.LTGRAY)
.title(model + ". Hello, world!")
.subtitle("Litho tutorial")
.build())
.build();
}
Finally combine these two new methods with DataDiffSection
by replacing our current onCreateChildren
method with:
@OnCreateChildren
static Children onCreateChildren(final SectionContext c) {
return Children.create()
.child(
DataDiffSection.<Integer>create(c)
.data(generateData(32))
.renderEventHandler(ListSection.onRender(c)))
.build();
}
Woah, what's this @OnEvent
stuff?? Where in the world did ListSection.onRender()
come from??
Here's a quick explanation of what's going on:
DataDiffSection
emits aRenderEvent
whenever an item needs to be rendered.When creating a
DataDiffSection
we give it our customRenderEventHandler
,ListSection.onRender(c)
.This custom event handler will call the
onRender
method defined inListSectionSpec
with all the right parameters whenever it receives aRenderEvent
.All of this event handler code is auto-generated from the
@OnEvent
annotation.You can read more Events and Litho's event handling system here.
Run the app. You should see the same thing as before.
2. Adding an H-scrollβ
Remember how we used RecyclerCollectionComponent
to create our list? Since RecyclerCollectionComponent
is itself a Component, we can create one inside a Section and easily nest scrolling lists! While it doesn't quite make sense to nest a vertical list inside another vertical list, it's quite common to nest a horizontal list inside a vertical list. We will do just that in this step.
Update onCreateChildren()
to add a SingleComponentSection
before the DataDiffSection
:
@OnCreateChildren
static Children onCreateChildren(final SectionContext c) {
final RecyclerConfiguration recyclerConfiguration =
ListRecyclerConfiguration.create()
.orientation(LinearLayoutManager.HORIZONTAL)
.reverseLayout(false)
.snapMode(SnapUtil.SNAP_TO_CENTER)
.recyclerBinderConfiguration(
RecyclerBinderConfiguration.create()
.recyclerBinderConfig(
RecyclerBinderConfig.create().crossAxisWrapMode(CrossAxisWrapMode.MatchFirstChild).build())
.build())
.build();
return Children.create()
.child(
SingleComponentSection.create(c)
.component(
RecyclerCollectionComponent.create(c)
.disablePTR(true)
.recyclerConfiguration(recyclerConfiguration)
.section(
DataDiffSection.<Integer>create(c)
.data(generateData(32))
.renderEventHandler(ListSection.onRender(c))
.build()))
.build())
.child(
DataDiffSection.<Integer>create(c)
.data(generateData(32))
.renderEventHandler(ListSection.onRender(c)))
.build();
}
We're seeing a couple new props for RecyclerCollectionComponent
here.
recyclerConfiguration
is a configuration object for setting the layout of the components and the snapping behavior for theRecyclerCollectionComponent
.CrossAxisWrapMode.MatchFirstChild
needs to be set for a horizontalRecyclerCollectionComponent
if you can't set a static height on it. The framework will measure the first child of theRecyclerCollectionComponent
and use that child's height as the height of the entire horizontal scrolling list.
Run the app now, you should see something like this:
Summaryβ
Congratulations on completing part 2 of this tutorial! This part of the tutorial introduced you to more advanced usages of Sections and should arm you with additional building blocks to help you build your own complex scrolling UI. Some more fun things you can try:
SingleComponentSection
has a few interesting props. See if you can make the horizontal scrolling row stick to the top- The
RenderEvent
event has other fields that are helpful when creating components for each row. See if you could make every 3rd row an h-scroll.
You can find the completed tutorial here. Be sure to check out this sample for more in-depth code as well as the Litho API documentation for additional information.