Migration Strategies
Prerequisitesβ
Before reading this page, you may find it helpful to review the following sections of the Tutorial:
- Setting up the Project - the required settings and dependencies to add Litho to your project.
- Component and Props - learn the basic Litho building blocks and create a component that uses props.
- Introducing Layout - become familiar with building layouts using Flexbox.
Adopting Litho in your appβ
Using Litho in a new surface is fairly straightforward: you can put a LithoView
at the root of your new Fragment
or Activity
and start writing your components.
However, adopting Litho within an existing surface needs to be done more incrementally and can require a bit more thought.
Litho components can interoperate with View
s in the same App or even on the same screen, so you can migrate View
-based screen to Litho incrementally and maintain a hybrid Component-View UI.
There are two common strategies for incrementally migrating to Litho: Bottom Up and Top-down, as detailed in the following sub-sections.
Let's use the simple UI shown below as an example:
This is how the XML/View
implementation of the above UI could look like:
XML | View |
---|---|
|
|
Bottom-upβ
With the bottom-up approach, you break down the UI into smaller pieces that can be converted incrementally. The View
or ViewGroup
in the original implementation is replaced by a LithoView
that you attach as child to the root ViewGroup
of your UI.
In the example component above, you can identify two UI blocks, which can be converted independently into two Litho components: Image and Text
Litho provides a library of widget components, which you can immediately start using. If your app has a custom design system that implements custom views for primitives such as Button
, Text
or Image
, you can start by creating Primitive Components) for them first; you can also reuse these components across the app to convert multiple screens to Litho.
Once you've completed the conversion of the View
s, you can coalesce all the individual Primitive Components into a single KComponent and use one LithoView
as the root of the UI.
Here is an example showing how the example component could be implemented with a bottom-up:
class MyCustomComponent : KComponent() {
override fun ComponentScope.render(): Component {
return Row {
child(
Image(
drawable = drawableRes(R.drawable.ic_launcher),
style = Style.width(100.dp).height(100.dp))
)
child(
Text(
text = "Hello there",
style = Style.alignSelf(YogaAlign.CENTER))
)
}
}
}
It's recommended to use the bottom-approach because this way you can use the full potential of Litho and its performance optimisations such as:
- Incremental mount
- View flattening
- Moving work to background thread
- View/Drawable pooling and preallocation
- Granular UI updates
Top-downβ
With the top-down approach, you replace the root ViewGroup
of your UI with a LithoView
and wrap the root View
representing the UI into a Primitive Component(). As you convert smaller parts of the UI into Components, you extract them out of the Primitive Component and into individual LithoView
s.
Here is how the example component could be implemented with the top-down approach:
class MyCustomComponent : PrimitiveComponent() {
override fun PrimitiveComponentScope.render(): LithoPrimitive {
return LithoPrimitive(
layoutBehavior = MyCustomLayoutBehavior,
mountBehavior = MountBehavior(ALLOCATOR) {},
style = null)
}
}
private val ALLOCATOR: ViewAllocator<LinearLayout> =
ViewAllocator { context ->
// Either instantiate the View
MyCustomLayout(context) as LinearLayout
// or inflate the XML
LayoutInflater.from(context)
.inflate(
R.layout.my_custom_layout,
null
) as LinearLayout
}
private object MyCustomLayoutBehavior : LayoutBehavior {
override fun LayoutScope.layout(
sizeConstraints: SizeConstraints
): PrimitiveLayoutResult {
val content = ALLOCATOR.createContent(androidContext)
content.measure(
sizeConstraints.toWidthSpec(),
sizeConstraints.toHeightSpec()
)
return PrimitiveLayoutResult(
width = max(sizeConstraints.minWidth, content.measuredWidth),
height = max(sizeConstraints.minHeight, content.measuredHeight))
}
}
This may be a good starting point, and in many cases it will be faster to implement than the bottom-up approach, especially when wrapping complex View
s containing lots of internal logic. However, components implemented with the top-down approach won't be as performant as the ones implemented with the bottom-up approach, because Litho won't be able to perform all of the available optimizations on components implemented with the top-down approach. For example:
- Less work can be moved to background thread - when the logic to initialise the
View
is encapsulated inside theView
, then it is necessary to do it on the main thread. - Possible corretness issues - mixing composite
View
s and component wrappers can be prone to accidental mutations of the internalView
state, causing the component andView
state to diverge.
Litho will still apply the following optimizations making the wrapped View
s more performant compared to not using Litho at all:
- Incremental Mount
- Pooling and preallocation
Some scenarios when the top-down approach is suitable include:
- Using Litho for the architecture of your surface and for writing new features, but existing
View
s might not be immediately converted to Litho. - Converting a list surface to Sections. The root of the surface is a
LithoView
rendering aRecyclerCollectionComponent
, while the individual list items can be eitherViews
or Litho Components. You can leverage the Litho Lists API for features such as asynchronous data diffing or granularRecyclerView.Adapter
updates before converting the entire UI to Litho.
More complex exampleβ
Consider the following UI as an example:
You can identify three UI blocks, which can be converted independently into three Litho Components:
- Header
- Media
- Footer
You'll have three LithoView
s in your UI to render the components.
These components will be composed of smaller widgets such as Text
or Image
, similar to how ViewGroup
s arrange smaller View
s.
Just like in the simpler example above - when the incremental conversion is complete, the individual Primitive Components can be merged into a single KComponent.
With the top-down approach, you'd replace the root ViewGroup
of your UI with a LithoView
and wrap the root View
representing the UI into a Primitive Component(). As you convert smaller parts of the UI into Components, you extract them out of the Primitive Component and into individual LithoView
s.