Props in Specs
This page covers the old Java Spec API. If the Spec API is not being used, refer to the Components page.
Litho uses a unidirectional data flow with immutable inputs. Following the name established by React, inputs to a Component
are known as props.
In the Spec API, props for a given Component
are the union of all arguments annotated with @Prop
. The value of the props can be accessed in all the methods that declare it as a @Prop
parameter. The same prop can be defined and accessed in multiple lifecycle methods. The annotation processor will ensure you're using consistent prop types and consistent annotation parameters across all the spec methods.
In the Kotlin API, props are just val
properties on a Component and can be accessed in the render
function and its hooks.
Prop immutabilityβ
The props of a Component are read-only. The Component's parent passes down values for the props when it creates the Component, and they cannot change throughout the lifecycle of the Component. If the props values must be updated, the parent has to create a new Component and pass down new values for the props.
Props should be immutable. Due to background layout, props may be accessed on multiple threads. The immutability of props ensures that no thread safety issues can occur during the component's lifecycle.
How to use Propsβ
Define Props on a Componentβ
The way props are defined is shown in the following sample:
- Kotlin API
- Spec API
Props are just val
properties of a Component.
class HelloComponent(private val name: String) : KComponent() {
override fun ComponentScope.render(): Component {
return Text(text = "Hello $name!")
}
}
Props are defined using the @Prop
annotation.
@LayoutSpec
public class FirstComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @Prop String name) {
return Text.create(c).text("Hello " + name + "!").build();
}
}
The props parameters will hold the value passed down from the Component's parent when the Component was created (or their default values).
Props are defined and used the same way as in LayoutSpec
s and MountSpec
s.
Set Props on a Componentβ
The following code shows how to set Props on a Component.
- Kotlin API
- Spec API
The prop can be passed by its name in the KComponent.
KotlinApiComponent(name = "Linda")
The annotation processor creates a Builder
class for the Component automatically, with setters, for each unique prop defined on the spec.
Values for these props can be passed down by calling the appropriate methods on the generated Component Builder:
FirstComponent.create(c).name("Linda").build();
Optional Props and Prop Defaultsβ
Litho provides a way to mark props as optional and define their default values:
- Kotlin API
- Spec API
In the Kotlin API, default values are always explicit because optional props are just normal constructor params with default arguments.
class PropDefaultKComponent(private val name: String = "John Doe") : KComponent() {
override fun ComponentScope.render(): Component {
return Text(text = "Hello $name!")
}
}
In the Spec API, a prop can be marked as optional by setting optional = true
flag on its @Prop
annotation, as seen in the example below.
It needs to be marked as such in all the methods that declare this prop, otherwise the annotation processor will throw an exception.
By default, if an optional prop's value is not provided when the component is created, a default value for its Java type is used (null
, 0
, or false
).
@LayoutSpec
public class OptionalPropComponentSpec {
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c, @Prop String name, @Prop(optional = true) @Nullable String location) {
if (location == null) {
return Text.create(c).text("Hello " + name + "!").build();
}
return Text.create(c).text("Hello " + name + " from " + location + "!").build();
}
@OnCreateInitialState
static void onCreateInitialState(
ComponentContext c, @Prop(optional = true) @Nullable String location) {
// ...
}
}
Instead of using the Java defaults, define an explicit default value for an optional prop. To create that default value, declare a static final
field with the same name and type as the original prop and mark it with the @PropDefault
annotation, as shown in the following example:
@LayoutSpec
class PropDefaultComponentSpec {
@PropDefault static final String name = "John Doe"; // default value for name
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @Prop(optional = true) String name) {
return Text.create(c).text(name).build();
}
}
Android Resources as Propsβ
When creating layouts, it's common to use values from Android's resource system, such as dimensions, colours, strings, and so on. The Litho Spec API provides convenient ways to set prop values from Android resources using annotations, as shown in the following examples:
- Kotlin API
- Spec API
Here, PropWithoutResourceTypeKComponent
is expected to take color
as an integer, size
in pixels, and a name
string.
class PropWithoutResourceTypeKComponent(
private val name: CharSequence,
private val size: Dimen,
@ColorInt private val color: Int
) : KComponent() {
override fun ComponentScope.render(): Component {
return Text(text = "Hello $name!", textSize = size, textColor = color)
}
}
If the props are to be set using resource values, it's recommended to do the following:
val res: Resources = context.resources
return PropWithoutResourceTypeKComponent(
name = res.getString(R.string.name_string),
size = dimenRes(R.dimen.primary_text_size),
color = ContextCompat.getColor(context.getAndroidContext(), R.color.primaryColor))
But Litho provides a nicer way to provide prop values via resource ids.
In the Kotlin API, there is no way to generate multiple variants of the same setter for a prop. However, helper functions can be used to retrieve the value of a resource by its ID, for example stringRes()
, dimenRes()
, colorRes()
, colorAttr
, and so on.
return PropWithResourceTypeKComponent(
name = stringRes(R.string.name_string),
size = dimenRes(R.dimen.primary_text_size),
color = colorRes(R.color.primaryColor))
Other supported functions are drawableRes()
, drawableAttr()
, drawableColor()
, colorAttr()
and intRes()
. These can be found in the code under Resources
In the following example, PropWithoutResourceTypeComponentSpec
is expected to take color
as an integer, size
in pixels, and a name
string.
@LayoutSpec
class PropWithoutResourceTypeComponentSpec {
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c, @Prop CharSequence name, @Prop int size, @Prop @ColorInt int color) {
return Text.create(c).text(name).textSizePx(size).textColor(color).build();
}
}
If these props are to be set using resource values, it's recommended to do the following:
final Resources res = c.getResources();
return PropWithoutResourceTypeComponent.create(c)
.name(res.getString(R.string.name_string))
.size(res.getDimensionPixelSize(R.dimen.primary_text_size))
.color(ContextCompat.getColor(c.getAndroidContext(), R.color.primaryColor))
.build();
But Litho provides a nicer way to provide prop values via resource ids. Just add the @Prop
parameter resType
, which then creates multiple builder methods on the component for the single prop.
@LayoutSpec
class PropWithResourceTypeComponentSpec {
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c,
@Prop(resType = ResType.STRING) CharSequence name,
@Prop(resType = ResType.DIMEN_SIZE) int size,
@Prop(resType = ResType.COLOR) int color) {
return Text.create(c).text(name).textSizePx(size).textColor(color).build();
}
}
With these params applied, for each resource prop PropWithResourceTypeComponentSpec
's builder will have not only the main setter (like name()
), but also its variants for resource ids with Res, Attr, Dip, Sp or Px suffixes, depending on prop's resource type (such as nameRes()
or sizePx()
).
return PropWithResourceTypeComponent.create(c)
.nameRes(R.string.name_string)
.sizePx(10)
.sizeDip(10)
.colorAttr(android.R.attr.textColorTertiary)
.build();
Other supported resource types are ResType.STRING_ARRAY
, ResType.INT
, ResType.INT_ARRAY
, ResType.BOOL
, ResType.COLOR
, ResType.DIMEN_OFFSET
, ResType.FLOAT
, and ResType.DRAWABLE
.
PropDefault
also supports values from Resources by setting a resType
and resId
.
The following example shows how to define a PropDefault
with a resource value:
@PropDefault(resType = ResType.DIMEN_SIZE, resId = R.dimen.vertical_spacing) static float spacingVertical;
Variable Arguments in Propsβ
Sometimes, passing a list of items can be a bit painful as it requires the Developer to create a list structure, add all the items to it, then pass the list to the Component:
- Kotlin API
- Spec API
In the Kotlin API, use the variable number of arguments (varargs) modifier provided by the language itself to achieve this behaviour.
class VariableArgumentPropKComponent(private vararg val names: String) : KComponent() {
override fun ComponentScope.render(): Component {
return Column() {
for (name in names) {
child(Text(text = name, textSize = 16.sp))
}
}
}
}
It can then be used as follows:
return VariableArgumentPropKComponent("One", "Two", "Three", "Four")
In the Spec API, the varArg
parameter in the @Prop
annotation aims to make this a little easier, enabling the Prop to be set using multiple methods. A list of strings can be set by using a method named for the prop: names(...)
. Alternatively, set one or many individual strings using a method named for a value of a varArg
parameter in the @Prop
annotation: name(...)
, as shown in the following example:
@LayoutSpec
public class VariableArgumentPropComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @Prop(varArg = "name") List<String> names) {
final Column.Builder builder = Column.create(c);
for (String name : names) {
builder.child(Text.create(c).text(name).textSizeSp(16));
}
return builder.build();
}
}
It can then be used as follows:
return VariableArgumentPropComponent.create(c)
.name("One")
.name("Two")
.name("Three")
.names(Arrays.asList("Four", "Five", "Six"))
.build();
Variable Arguments also work with Android resources as props:
- Kotlin API
- Spec API
In Kotlin, variable arguments can be used with Android resources by using the helper functions to resolve the value by resource ID. The following example shows how to provide multiple strings as props, mixing String
variables and Android string resources:
return VariableArgumentPropKComponent(
"One", stringRes(R.string.app_name), stringRes(R.string.name_string))
Given the following component:
@LayoutSpec
public class VariableArgumentWithResourceTypeSpec {
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c, @Prop(varArg = "name", resType = ResType.STRING) List<String> names) {
final Column.Builder builder = Column.create(c);
for (String name : names) {
builder.child(Text.create(c).text(name).textSizeSp(16));
}
return builder.build();
}
}
Multiple strings can be added by mixing String
variables with Android string resources:
return VariableArgumentWithResourceType.create(c)
.name("One")
.nameRes(R.string.app_name)
.nameRes(R.string.name_string)
.build();