Starting in Tiles 1.2, you can stream platform data updates using dynamic expressions. You can then associate these updates with animations in your tiles. Your app gets updates to this value every second.
Using dynamic expressions, you don't need to refresh the entire tile when its content changes. To create a more engaging experience in your tiles, animate those dynamic objects.
Associate dynamic expressions with data sources
The androidx.wear.protolayout
and androidx.wear.protolayout.material
namespaces contain many classes whose fields accept dynamic expressions.
Several examples include the following:
- Several length values, including the length of an
Arc
object and the length of aCircularProgressIndicator
object. - Any color, such as the content color of a
Button
object. - Many string values, including the content of a
Text
object, the content of aLayoutElementsBuilders.Text
object, and the content description of aCircularProgressIndicator
object.
To use a dynamic expression as a possible value for an element in your tile, use
the element's corresponding *Prop
dynamic property type and pass in the data
source to the dynamic property type's builder class's setDynamicValue()
method.
Tiles support these dynamic property types:
- For linear dimensions, measured in display-independent pixels, use
DimensionBuilders.DpProp
. - For angular dimensions, measured in degrees, use
DimensionBuilders.DegreesProp
. - For string values, use
TypeBuilders.StringProp
. - For color values, use
ColorBuilders.ColorProp
. - For floating-point values, use
TypeBuilders.FloatProp
.
When you use a dynamic expression that affects physical dimensions—any value in
a tile except for color—you must also specify a set of related constraints, such
as a string format. These constraints allow the system renderer to determine the
maximum amount of space that a value could occupy within your tile. Usually, you
specify these constraints at the element level, not at the dynamic expression
level, by calling a method that starts with setLayoutConstraintsForDynamic*
.
The following code snippet shows how to display updates to a heart rate using 3
digits, with a fallback value of --
:
Kotlin
import androidx.wear.protolayout.material.Text public override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = Futures.immediateFuture(Tile.Builder() .setResourcesVersion(RESOURCES_VERSION) .setFreshnessIntervalMillis(60 * 60 * 1000) // 60 minutes .setTileTimeline(Timeline.fromLayoutElement( Text.Builder(this, TypeBuilders.StringProp.Builder("--") .setDynamicValue(PlatformHealthSources.heartRateBpm() .format() .concat(DynamicBuilders.DynamicString.constant(" bpm"))) .build(), StringLayoutConstraint.Builder("000") .build() ).build() ) ).build() )
Java
import androidx.wear.protolayout.material.Text; @Override protected ListenableFuture<Tile> onTileRequest( @NonNull TileRequest requestParams ) { return Futures.immediateFuture(new Tile.Builder() .setResourcesVersion(RESOURCES_VERSION) .setFreshnessIntervalMillis(60 * 60 * 1000) // 60 minutes .setTileTimeline(Timeline.fromLayoutElement( new Text.Builder( this, new TypeBuilders.StringProp.Builder("--") .setDynamicValue(PlatformHealthSources.heartRateBpm() .format() .concat(DynamicBuilders.DynamicString.constant(" bpm"))) .build(), new StringLayoutConstraint.Builder("000") .build() ).build()) ).build() ); }
Use a small number of expressions within a single tile
Wear OS places a limit on the number of expressions that a single tile can have. If a tile contains too many total dynamic expressions, dynamic values are ignored, and the system falls back to the static values that you provide to the respective dynamic property types.
You can safely add the following set of expressions to a tile, because there aren't many total expressions. Therefore, the tile behaves correctly:
Kotlin
val personHealthInfo = DynamicString.constant("This person has walked ") .concat(PlatformHealthSources.dailySteps() .div(1000) .format()) .concat("thousands of steps and has a current heart rate ") .concat(PlatformHealthSources.heartRateBpm() .format()) .concat(" beats per minute")
Java
DynamicString personHealthInfo = DynamicString.constant("This person has walked ") .concat(PlatformHealthSources.dailySteps() .div(1000) .format()) .concat("thousands of steps and has a current heart rate ") .concat(PlatformHealthSources.heartRateBpm() .format()) .concat(" beats per minute");
However, this tile might have too many expressions:
Kotlin
// Note that this template is applied as many times as the loop iterates. // The system doesn't reuse dynamic expressions. val dynamicStringTemplate = PlatformHealthSources.dailySteps() .div(1000) .format() for (person in people) { // SomeProperty .setDynamicValue( DynamicBuilders.DynamicString.constant("Steps for ") .concat(person) .concat(" are ") .concat(dynamicStringTemplate) ) }
Java
// Note that this template is applied as many times as the loop iterates. // The system doesn't reuse dynamic expressions. DynamicString dynamicStringTemplate = PlatformHealthSources.dailySteps() .div(1000) .format(); for (int i = 0; i < people.size(); i++) { // SomeProperty .setDynamicValue( DynamicBuilders.DynamicString.constant("Steps for ") .concat(people[i]) .concat(" are ") .concat(dynamicStringTemplate) ); }
Consolidate dynamic data into a state object
You can consolidate the latest set of updates from data sources into a state, which you pass over to your tile for value rendering.
To use state information in your tiles, complete these steps:
Establish a set of keys that represent the different values of your tile's state. This example creates keys for water intake and a note:
Kotlin
companion object { val KEY_WATER_INTAKE = AppDataKey<DynamicInt32>("water_intake") val KEY_NOTE = AppDataKey<DynamicString>("note") }
Java
private static final AppDataKey<DynamicInt32> KEY_WATER_INTAKE = new AppDataKey<DynamicInt32>("water_intake"); private static final AppDataKey<DynamicString> KEY_NOTE = new AppDataKey<DynamicString>("note");
In your implementation of
onTileRequest()
, callsetState()
and establish initial mappings from each key to a particular dynamic data value:Kotlin
override fun onTileRequest(requestParams: TileRequest): ListenableFuture<Tile> { val state = State.Builder() .addKeyToValueMapping(KEY_WATER_INTAKE, DynamicDataBuilders.DynamicDataValue.fromInt(200)) .addKeyToValueMapping(KEY_NOTE, DynamicDataBuilders.DynamicDataValue.fromString("Note about day")) .build() // ... return Futures.immediateFuture(Tile.Builder() // Set resources, timeline, and other tile properties. .setState(state) .build() )
Java
@Override protected ListenableFuture<Tile> onTileRequest( ListenableFuture<Tile> { State state = new State.Builder() .addKeyToValueMapping(KEY_WATER_INTAKE, DynamicDataBuilders.DynamicDataValue.fromInt(200)) .addKeyToValueMapping(KEY_NOTE, DynamicDataBuilders.DynamicDataValue.fromString("Note about day")) .build(); // ... return Futures.immediateFuture(Tile.Builder() // Set resources, timeline, and other tile properties. .setState(state) .build() ); }
When you create your layout, in a place where you want to show this data from state, use a
Dynamic*
type object. You can also callanimate()
to show an animation from the previous value to the current value:Kotlin
DynamicInt32.from(KEY_WATER_INTAKE).animate()
Java
DynamicInt32.from(KEY_WATER_INTAKE).animate();
When needed, you can also update the state with new values. This can be part of a tile's
LoadAction
.In this example, the water intake value is updated to
400
:Kotlin
val loadAction = LoadAction.Builder() .setRequestState( State.Builder() .addKeyToValueMapping( KEY_WATER_INTAKE, DynamicDataBuilders.DynamicDataValue.fromInt(400) ) .build() ) .build()
Java
LoadAction loadAction = new LoadAction.Builder() .setRequestState( new State.Builder() .addKeyToValueMapping( KEY_WATER_INTAKE, DynamicDataBuilders.DynamicDataValue.fromInt(400) ).build() ).build();
Recommended for you
- Note: link text is displayed when JavaScript is off
- Migrate to ProtoLayout namespaces
- Get started with tiles
- Other considerations