Skip to main content

Advanced: Writing your own DiffSection

In this document we will describe how to build your own DiffSection. The Sections API already provides two implementations that cover most frequent use cases in SingleComponentSection and DataDiffSection. You should write your own DiffSection if the given implementations do not suffice for your use case, as the complexity and chances of introducing subtle errors are both high.

DiffSectionSpec#

A diff section spec defines a section that explicitly outputs insert, update, and remove changes on the section hierarchy.

Diff section specs explicitly manage insertions, removals, and updates that a section performs whenever its states and props change. You will find Diff Sections at the leaves of every section tree as they are the sections that actually specify the changes to be made to a list.

One example where you might want a custom diff section is if you receive the data you want to display in the form of incremental updates or diffs. This might happen if you're using a specialised diffing algorithm to process your data.

note

DataDiffSection utilises a familiar Android's DiffUtil.

Let's use the example of SingleComponentSection to describe how to write diff section specs. Here is a snippet of SingleComponentSectionSpec:

@DiffSectionSpec
class SingleComponentSectionSpec {
@OnDiff
static void onCreateChangeSet(
SectionContext c,
ChangeSet changeSet,
@Prop Diff<Component> component,
...) {
if (component.getNext() == null) {
changeSet.delete(0);
} else if (component.getPrevious() == null) {
changeSet.insert(
0,
ComponentRenderInfo.create()
.component(component.getNext())
...
.build());
} else {
changeSet.update(
0,
ComponentRenderInfo.create()
.component(component.getNext())
...
.build());
}
}
}

As you can see, diff section specs use the @DiffSectionSpec annotation. Implementing a diff section spec requires little boilerplate. You only have to write one method annotated with @OnDiff.

The method annotated with @OnDiff must have as its first and second argument a SectionContext and a ChangeSet respectively. Following these two arguments, your @OnDiff method can also accept any number of arguments annotated with @Prop or @State.

These props and state have a special type: Diff<T>. If your prop is defined in another annotated method like @Prop String prop1, it must be defined as @Prop Diff<String> prop1 when being used in the @OnDiff method. The reason for this Diff<T> type wrapper is so we can compare previous prop values with new prop values when computing changes.

Making changes to the list with ChangeSet#

The ChangeSet argument of the @OnDiff method is used by the Diff section spec to specify how the section changes in response to new data. The @OnDiff method will always be called with the current and previous props and state values (hence the Diff<T> type). The expectation is that you'd be able to use the current and previous values to determine how to update the items being rendered.

When you've determined what changes need to be made, you should call the corresponding method on the ChangeSet object. These methods correspond to RecyclerView.Adapter's notifyItem* methods. You can get a quick idea of how this works in SingleComponentSectionSpec#onDiff:

  • If we don't have a new Component (component.getNext() == null) then we want to change the list by removing that row.
  • Else if we have a new Component and no previous component, we want to insert a new row.
  • If both an old component and a new component exists, we just want to update that row to the new component.

Note, the indexes used in the ChangeSet method calls, they're relative to the current section. Index 0 in SingleComponentSectionSpec might actually be index 100 in the final list depending on the section hierarchy. The framework will take care of translating local indexes to global indexes when processing the ChangeSet.