Normally, when the value of a @Prop within a ComponentTree changes, the framework needs to compute layout and mount the Component again. However, there is a category of @Props that do not affect layout, thus when the value of the @Prop changes, the framework can take a “shortcut”: apply the new value to the mounted UI element that represents the Component right away. We call such properties “dynamic”. DynamicValue<T> is the interface that makes it possible.

Common Dynamic Props

The dynamic properties that are available for all Components are:

  • Alpha
  • Scale X/Y
  • Translation X/Y
  • Background Color

To use this, all you need to do is to create and pass a DynamicValue<T> object to the corresponding Component.Builder method. Normally, you would hold on to this object, and use its set() method to update the actual value.

In the following sample we have a Component that renders a yellow square in the middle of the screen. We also have two regular Android SeekBars “outside” of the Components hierarchy that control the alpha and the scale levels of the square.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
 * MyComponentSpec.java
 */
@LayoutSpec
public class MyComponentSpec {

  @OnCreateLayout
  static Component onCreateLayout(
      ComponentContext c,
      @Prop DynamicValue<Float> alphaDV,
      @Prop DynamicValue<Float> scaleDV) {
    Component yellowSquare = Rect.create(c)
                .color(YELLOW)
                .alpha(alphaDV)
                .scaleX(scaleDV)
                .scaleY(scaleDV)
                .build();

    return Column.create(c)
        .child(yellowSquare)
        .alignItems(YogaAlign.CENTER)
        .justifyContent(YogaJustify.CENTER)
        .build();
  }
}

/**
 * MyActivity.java
 */
public class MyActivity extends Activity
    implements SeekBar.OnSeekBarChangeListener {

  private DynamicValue<Float> mAlphaDV;
  private DynamicValue<Float> mScaleDV;

  private TextView mAlphaLabel;
  private TextView mScaleLabel;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mAlphaDV = new DynamicValue<>(1f);
    mScaleDV = new DynamicValue<>(1f);

    setContentView(R.layout.activity);

    ComponentContext c = new ComponentContext(this);
    Component component = MyComponent.create(c)
			.alphaDV(mAlphaDV)
			.scaleDV(mScaleDV)
			.build();

    LithoView lithoView = findViewById(R.id.lithoView);
    lithoView.setComponent(component);

    mAlphaLabel = findViewById(R.id.alphaValue);
    mScaleLabel = findViewById(R.id.scaleValue);

    SeekBar alphaSeekBar = findViewById(R.id.alphaSeekBar);
    alphaSeekBar.setMax(100);
    alphaSeekBar.setProgress(100);
    alphaSeekBar.setOnSeekBarChangeListener(this);

    SeekBar scaleSeekBar = findViewById(R.id.scaleSeekBar);
    scaleSeekBar.setMax(150);
    scaleSeekBar.setProgress(50);
    scaleSeekBar.setOnSeekBarChangeListener(this);
  }

  @Override
  public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    if (seekBar.getId() == R.id.alphaSeekBar) {
      // Update alpha value and label
      float alpha = progress / 100f;
      mAlphaDV.set(alpha);
      mAlphaLabel.setText(Float.toString(alpha));
    } else {
      // Update scale value and label
      float scale = (progress + 50) / 100f;
      mScaleDV.set(scale);
      mScaleLabel.setText(Float.toString(scale));
    }
  }
}

Notice that:

  1. On lines 43-44, in MyActivity.java, we create DynamicValue objects
  2. On lines 50-51, in MyActivity.java, we supply the DynamicValues to the MyComponent (just as regular @Props).
  3. On lines 14-16, in MyComponentSpec.java, we pass DynamicValue<Float> objects to alpha(), scaleX() and scaleY() methods of Component.Builder to control the corresponding properties of the Rect component.
  4. On lines 76, 81, in MyActivity.java, we use the DynamicValue objects to keep the state of the SeekBars and the value of the properties they control in sync.

Custom Dynamic Props for MountSpecs

You may have your own MountSpec which has @Props that also do not affect layout. It is possible to control the values of those properties in similar way using DynamicValue interface. However, in this case you will need to tell framework how to apply the value of the @Prop to the mounted element.

To show you how to do this, let us consider a MountSpec that mounts a ClockView and defines time property, which it passes to the View in @OnMount.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * ClockComponentSpec.java
 */
@MountSpec
class ClockComponentSpec {

  @OnCreateMountContent
  static ClockView onCreateMountContent(Context c) {
    return new ClockView(c);
  }

  @OnMount
  static void onMount(ComponentContext c, ClockView clockView, @Prop long time) {
    clockView.setTime(time);
  }
}

Notice that the value of the time property does not affect layout, it only controls how the ClockView draws clock hands. However, every time you want to update it the framework will have to go through LayoutState and MountState.

Here is how we can fix this by converting to Dynamic Props and, at the same time, get a more convenient interface to adjust the value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
 * ClockComponentSpec.java
 */
@MountSpec
class ClockComponentSpec {

  @OnCreateMountContent
  static ClockView onCreateMountContent(Context c) {
    return new ClockView(c);
  }

  @OnBindDynamicValue
  static void onBindTime(
	  ClockView clockView,
	  @Prop(dynamic = true) long time) {
    clockView.setTime(time);
  }
}

First thing you need to do is to mark the @Prop as dynamic - line 15. Once you have done this, the framework will generate an additional method to the builder of your Component that takes a DynamicValue. At the same time it will keep the version of this method that takes “static” value, if you choose to use this in some situations.

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * ClockComponent.java (generated)
 */
 public final class ClockComponent extends Component {
   ...
   public static final class Builder extends Component.Builder<Builder> {
      ...
      public Builder time(DynamicValue<Long> time) {...}
      public Builder time(long time) {...}
      ...
   }
}

Second thing is to create a @OnBindDynamicValue method - lines 12-17 in ClockComponentSpec.java - that should set the value to the mounted content. This method should always takes 2 arguments - mounted content, and the @Prop itself. You need to create one such method for every dynamic @Prop you define. Then, it is the responsibility of the framework to invoke these methods to keep changes to the DynamicValue.

Here you find the full implementation of the sample above.

Edit on GitHub