KComponent and Props
This page details how to convert existing components with @LayoutSpecs
to Kotlin KComponent
.
For information regarding the main Components and Props, refer to the Components and Props page of the Tutorial.
@OnCreateLayout
β
The most fundamental change in the Litho Kotlin API is that code generation is no longer used. This means there's no need to mark components as @LayoutSpec
s or declare any other annotations in the class. Remember to leave off the 'Spec' at the end of the component name!
The static method marked with @OnCreateLayout
is replaced with a render()
function override, which returns what the component should render.
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
class TestComponent : KComponent() {
override fun ComponentScope.render(): Component? {
TODO("Return a component")
}
}
Propsβ
Props are now just val
properties on a component:
- Kotlin
- Java
class HelloComponent(private val name: String) : KComponent() {
override fun ComponentScope.render(): Component {
return Text(text = "Hello $name!")
}
}
@LayoutSpec
public class FirstComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @Prop String name) {
return Text.create(c).text("Hello " + name + "!").build();
}
}
Specify props to components as named arguments:
Text(text = "Hello $name!")
Common Propsβ
A Style object is an immutable, linked-list of properties to apply to a component.
Common props, such as margin
, clickHandler
, and alpha
, are now set using Style.
In the Spec API, these common props could be applied to any component on their builder. However, in the Kotlin API, a component must declare it accepts a Style
as a prop. It can then pass that Style
object to one of the Components it renders to.
The following Java and Kotlin code is functionally equivalent and results in OuterTextComponent
resolving to a Text
component which has alpha, padding, and a margin set on it:
- Kotlin API
- Spec API
class OuterTextComponent : KComponent() {
override fun ComponentScope.render(): Component {
return InnerTextComponent(style = Style.margin(all = 8.dp))
}
}
class InnerTextComponent(private val style: Style? = null) : KComponent() {
override fun ComponentScope.render(): Component {
return Text(
style = Style.padding(all = 8.dp).alpha(.5f) + style,
text = "I accept style from a parent!")
}
}
@LayoutSpec
class OuterTextComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c) {
return InnerTextComponent.create(c).marginDip(YogaEdge.ALL, 8).build();
}
}
@LayoutSpec
class InnerTextComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c) {
return Text.create(c)
.text("I accept style from a parent!")
.paddingDip(YogaEdge.ALL, 8)
.alpha(.5f)
.build();
}
}
If it isn't obvious that these are equivalent, it helps to understand that in the spec API, a series of @LayoutSpec
components resolve to a single 'node' ending with either a Row/Column or a @MountSpec
component. All components resolving to this node share a single set of common props.
For an illustration of this process, see the 6-minute YouTube video Litho Lessons: Component to Screen.
This API also gives flexibility to accept multiple Style objects to apply to different children since all common props are passed down explicitly!
Java - Kotlin compatibilityβ
To pass down a Style
from a Java class to a Kotlin KComponent
, use StyleCompat
:
@LayoutSpec
class OuterStyleComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c) {
return new InnerTextComponent(StyleCompat.marginDip(YogaEdge.ALL, 8).build());
}
}
For the other way around, passing style
from Kotlin code to a Java Component Spec
, use .kotlinStyle()
, which is equivalent to setting all the common props the Style defines:
class OuterStyleKComponent : KComponent() {
override fun ComponentScope.render(): Component {
val style = Style.margin(all = 8.dp)
return OuterStyleComponent.create(context).kotlinStyle(style).build()
}
}
Working with Style
β
In the above example, +
is used to combine the style passed from OuterTextComponent
and the styles that InnerTextComponent
defines. The +
operator combines two styles into a single style without mutating either:
val alphaStyle = Style.alpha(1f)
val combinedStyle = alphaStyle + Style.padding(all = 8.dp).margin(all = 8.dp)
// Result:
// alphaStyle: (alpha: 1f)
// combinedStyle: (alpha: 1f) <- (padding-all: 8.dp) <- (margin-all: 8.dp)
Note that ordering around +
matters: if a style property is defined twice, the last definition wins:
val alphaStyle = Style.alpha(1f)
val combinedStyle = alphaStyle + Style.padding(all = 8.dp).alpha(.5f)
// Result:
// combinedStyle will apply padding of 8.dp and alpha of .5f
For reference, in the Java Spec API, if a parent component and a child component set the same common prop, the parent's definition wins. The equivalent in the Kotlin API is to have the parent style on the right-hand side of the +
operator.
TreePropsβ
A TreeProp is a special type of prop that's transparently passed from a parent component to its children. It provides a convenient way to share contextual data or utilities in a tree without having to explicitly pass val properties to every component in the hierarchy.
Declaring a TreePropβ
In order to declare a TreeProp, use TreePropProvider
:
return TreePropProvider(
typefaceTreeProp to Typeface.DEFAULT_BOLD,
titleTreeProp to getTextTitle(),
legacyTreePropOf<Int>() to Color.RED) {
TreePropsChildComponent()
}
One TreeProp can be delcared for any one given type. If a child of ParentComponent also defines a TreeProp of the given type, it will override the value of that TreeProp for all its children (but not for itself).
Using a TreePropβ
The child component can access the TreeProp value through a ComponentScope.getTreeProp<>()
method that has the same type that was declared in the parents TreePropProvider
call:
val typeface = typefaceTreeProp.value
val title = titleTreeProp.value
val color = getTreeProp<Int>()
Handlesβ
A Handle is a unique identifier that can be used to trigger events (though triggers themselves are not yet supported in the Kotlin API).
Creating a Handleβ
In order to create a Handle, use Handle()
and save it as either a CachedValue
or State
:
val anchorHandle = useCached { Handle() }
Using a Handleβ
To add a Handle to a component, wrap the component in a handle
function. This sets the handle on the given component.
With a direct reference to the handle, it can be used to trigger events for that component:
handle(anchorHandle) {
Text(
text = "Tooltip anchor",
style = Style.margin(top = 50.dp).onVisible { showToolTip(anchorHandle) })
}
private fun ComponentScope.showToolTip(anchorHandle: Handle) {
LithoTooltipController.showTooltipOnHandle(
context, createTooltip("Example Tooltip"), anchorHandle, 0, 0)
}