Architecture Overview
Core Concepts
- Component. A bundle of immutable data (Props) and pure functions that can transform the props into UI.
- LithoView. A subclass of
android.view.ViewGroupthat is used to render aComponent. - ComponentTree. A class that holds a single root
Componentand controls its lifecycle. - LayoutState. Responsible for calculating a layout and holding onto the output of the calculation.
- MountState. Responsible for mounting the
Componentinto aLithoView. EachLithoViewhas exactly oneMountState. - InternalNode. Class which represents a single node in the layout of a
Component. - LayoutOutput. Lightweight representation of the output of the layout pass. The
LayoutStateproduces a List ofLayoutOutputs that are then mounted by theMountState.
Litho Lesson: Component to Screen
We recommend you start by watching this video: it covers most of the core concepts above, as well as the fundamentals of how a Litho component gets turned into a View on screen.
Layout
Initializing Layout
Layout calculation is controlled by the ComponentTree. There are two mains ways that a ComponentTree will kick off a layout calculation:
- If the bounds are known in advance, then the user can call
setSizeSpec/setSizeSpecAsyncdirectly. - When the
ComponentTreeis set on aLithoView, thenLithoView#onMeasure()will cause a layout to be calculated, unless theComponentTreealready has a valid layout (e.g. from an earliersetSizeSpecAsynccall).
Calculating Layout
LayoutState#createTree. The first step in calculating layout is creating the tree of components (a tree ofInternalNodes). This is done by recursively resolving each Component in the hierarchy, which forRowandColumnmeans creating a newInternalNodeand adding each of their children, and for all other Components just means callingonCreateLayout.LayoutState#measureTree. The second step in the calculation is measuring the tree. To do this theInternalNodes just defer to their underlying Yoga nodes, and Yoga does the calculation for us.LayoutState#collectResults. Now that we have created and measured the tree, we collect the results into a lightweight representation of elements that need mounting and throw away theInternalNodetree.
Layout Diffing
When calculating new layout on a ComponentTree that already has an existing layout, it is common that much of the layout is actually quite similar to before (e.g. you might change the color of an icon when you click on it, but leave the remaining UI the same). In order to take advantage of the similarities in cases such as this, we do something called layoutDiffing. Each LayoutState that we create has a tree of DiffNodes that store the results of the layout that we calculated, including measurements. When creating a new LayoutState for a ComponentTree that already has one, we use the DiffNode tree to skip work where possible.
Mount
Mount (MountState#mount) takes place for the first time when the LithoView is first laid out, and again every time the visible portion of the LithoView changes (provided that incremental mount is enabled). The mount step receives a Rect indicating the current visible area of the LithoView, and it uses that Rect to determine which of the LayoutOutputs from the Layout phase need to be either mounted (if they are now on the screen and they weren't before) or unmounted (if they are no longer on the screen).
For each Component that needs to be mounted, we first acquire the View/Drawable defined in the MountSpec's onCreateMountContent method. If possible, we acquire the mount content from our recycling pool (which is stored on a per-context basis), but if the pool is empty, then we create a new one. After that, we call mount, and then bind. When unmounting, we call unbind and then unmount.
Each time mount is called, we also call MountState#processVisibilityOutputs, which determines whether any visibility events need to be fired, and fires them if so.
Mounting (including firing visibility events) is bound to the UI thread, so operations called here should be as lightweight as possible.
Mount Diffing
Mount diffing is a technique that Litho uses to avoid unmounting and mounting content when it is not necessary. Before mounting, we look at the currently mounted content and compare it with the content that we want to mount. If the currently mounted content does not need updating, then we don't call unmount/mount.