TreeProps
This page covers the old Java Spec API. If the Spec API is not being used, refer to the TreeProps section of the 'Types of Props' page.
A TreeProp is a special type of prop that is 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 props to every component in a hierarchy.
A good example of a TreeProp is a 'prefetcher', which fetches network images ahead of render time. Since images are commonplace, the prefetcher is widely used. The prefetcher implementation might be defined for any component that needs to use it, without having to pass it as a prop in the entire component hierarchy.
Declaring a TreePropβ
- Kotlin API
- Spec API
In order to declare a TreeProp, use TreePropProvider
:
class ParentKComponent(private val prefetcher: Prefetcher, private val imageUri: Uri) :
KComponent() {
override fun ComponentScope.render(): Component? {
return TreePropProvider(Prefetcher::class.java to prefetcher) { ChildKComponent(imageUri) }
}
}
Each TreeProp is declared and created from a method annotated with @OnCreateTreeProp
:
@LayoutSpec
public class ParentComponentSpec {
@OnCreateTreeProp
static Prefetcher onCreatePrefetcher(ComponentContext c, @Prop Prefetcher prefetcher) {
return prefetcher;
}
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @Prop Uri imageUri) {
return ChildComponent.create(c).imageUri(imageUri).build();
}
}
Only one TreeProp can be declared for any one given type. If a child of ParentComponent
also defines a TreeProp of type Prefetcher
, it will override the value of that TreeProp for all its children (but not for itself).
Using a TreePropβ
- Kotlin API
- Spec API
The child component can access the TreeProp value through a ComponentScope.getTreeProp<Type>()
method call, where Type
is the same type that was provided in the TreePropProvider
call in one of its parents.
class ChildKComponent(private val imageUri: Uri) : KComponent() {
override fun ComponentScope.render(): Component {
val prefetcher = getTreeProp<Prefetcher>()
prefetcher?.prefetch(imageUri)
// ...
return Text("Prefetch image when this component is created")
}
}
The child component can access the TreeProp value through a parameter annotated with @TreeProp
that has the same type as the return type of one of the @OnCreateTreeProp
methods defined in the ancestors of child component.
@LayoutSpec
class ChildComponentSpec {
@OnCreateLayout
static Component onCreateLayout(
ComponentContext c, @Prop Uri imageUri, @Nullable @TreeProp Prefetcher prefetcher) {
if (prefetcher != null) {
prefetcher.prefetch(imageUri);
}
// ...
return Column.create(c)
.child(Text.create(c).text("Prefetch image when this component is created"))
.build();
}
}
Once created, the TreeProp value is passed down to all children but isn't accessible from the component that created the TreeProp.
To access a TreeProp from the component that created it, transform it into State as follows:
- Kotlin API
- Spec API
class ParentComponentTreePropAsStateKComponent() : KComponent() {
override fun ComponentScope.render(): Component? {
val importantHelper = useState { ImportantHelper() }
return TreePropProvider(ImportantHelper::class.java to importantHelper) {
Column() { child(Text(text = "ImportantHelper can be used as State in render function")) }
}
}
}
@LayoutSpec
public class ParentComponentTreePropAsStateSpec {
@OnCreateInitialState
static void createInitialState(ComponentContext c, StateValue<ImportantHelper> helper) {
helper.set(new ImportantHelper());
}
@OnCreateTreeProp
static ImportantHelper onCreateHelper(ComponentContext c, @State ImportantHelper helper) {
return helper;
}
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @State ImportantHelper helper) {
return Column.create(c)
.child(
Text.create(c).text("ImportantHelper can be used as State in onCreateLayout").build())
.build();
}
}
TreeProps with Listsβ
TreeProps can be used in components, sections and lazy collections. They can even be modified between them.
The following code is an example of a logging data structure that is passed down from the root component to capture information about the hierarchy.
public class LogContext {
public final String tag;
public LogContext(String tag) {
this.tag = tag;
}
public static LogContext append(@Nullable LogContext parentContext, String tag) {
if (parentContext == null) {
return new LogContext(tag);
}
return new LogContext(parentContext.tag + ":" + tag);
}
public String toString() {
return tag;
}
}
Immutable TreeProps are easier to understand so try to follow that design pattern whenever possible.
The following diagram shows the component hierarchy.
- Kotlin API
- Spec API
Start by setting up the RootComponent
with the LazyList
as one of its children:
class RootKComponent : KComponent() {
override fun ComponentScope.render(): Component? {
return TreePropProvider(LogContext::class.java to LogContext("root")) {
Column() {
child(LeafKComponent())
child(LazyList(style = Style.height(500.dp)) { child(TopGroupKComponent()) })
}
}
}
}
The TopGroupKComponent takes in the root TreeProp and adds its "top"
tag to it:
class TopGroupKComponent : KComponent() {
override fun ComponentScope.render(): Component? {
val parentLogContext = getTreeProp<LogContext>()
val topGroupLogContext = LogContext.append(parentLogContext, "top")
return TreePropProvider(LogContext::class.java to topGroupLogContext) {
Column {
child(LeafKComponent())
child(BottomGroupKComponent())
}
}
}
}
The bottom part here has been omitted for brevity, but it can be found in the repository under samples.
The leaf node renders the TreeProp as text in the example case but would normally perform some sort of logging based on the context:
class LeafKComponent() : KComponent() {
override fun ComponentScope.render(): Component {
val parentLogContext = getTreeProp<LogContext>()
return Text(text = LogContext.append(parentLogContext, "leaf").toString())
}
}
Start by setting up the RootComponent
with the RecyclerCollectionComponent
as one of its children:
@LayoutSpec
public class RootComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c) {
return Column.create(c)
.child(LeafComponent.create(c))
.child(
RecyclerCollectionComponent.create(c)
.section(TopGroupSection.create(new SectionContext(c)).build())
.heightDip(500)
.build())
.build();
}
@OnCreateTreeProp
static LogContext onCreateTestTreeProp(ComponentContext c) {
return new LogContext("root");
}
}
The TopGroupSection takes in the root TreeProp and adds its "top"
tag to it:
@GroupSectionSpec
public class TopGroupSectionSpec {
@OnCreateChildren
protected static Children onCreateChildren(SectionContext c) {
return Children.create()
.child(SingleComponentSection.create(c).component(LeafComponent.create(c)))
.child(BottomGroupSection.create(c).build())
.build();
}
@OnCreateTreeProp
static LogContext onCreateTestTreeProp(SectionContext c, @TreeProp LogContext parentLogContext) {
return LogContext.append(parentLogContext, "top");
}
}
The bottom part has been omitted here for brevity, but it can be found in the repository under samples.
The leaf node simply renders the TreeProp as text in our example case here, but would normally perform some sort of logging based on the context:
@LayoutSpec
public class LeafComponentSpec {
@OnCreateLayout
static Component onCreateLayout(ComponentContext c, @TreeProp LogContext parentLogContext) {
return Text.create(c).text(LogContext.append(parentLogContext, "leaf").toString()).build();
}
}
The on-screen result is three rows of text that read:
"root:leaf"
"root:top:leaf"
"root:top:bottom:leaf"
This illustrates how TreeProps propagate through both component and section trees and can be used to selectively share information with their children.