Skip to main content

KComponent and Props

note

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 @LayoutSpecs 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:

class HelloComponent(private val name: String) : KComponent() {

override fun ComponentScope.render(): Component {
return Text(text = "Hello $name!")
}
}

Specify props to components as named arguments:

Text(text = "Hello $name!")

Common Props​

info

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:

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!")
}
}

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.

note

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(
Typeface::class.java to Typeface.DEFAULT_BOLD,
String::class.java to getTextTitle(),
Int::class.java to Color.RED) {
TreePropsChildComponent()
}
note

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 color = getTreeProp<Int>()
val typeface = getTreeProp<Typeface>()
val title = getTreeProp<String>()

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)
}