Mostrar atualizações dinâmicas em Blocos

Na versão 1.2 da API Tiles e mais recentes, é possível transmitir atualizações de dados da plataforma usando expressões dinâmicas. Você também pode associar essas atualizações a animações nos blocos. Seu app recebe atualizações para esse valor a cada segundo.

Com as expressões dinâmicas, você não precisa atualizar todo o Bloco quando o conteúdo dele mudar. Para criar uma experiência mais envolvente nos Blocos, anime esses objetos dinâmicos.

Associar expressões dinâmicas a fontes de dados

Os namespaces androidx.wear.protolayout e androidx.wear.protolayout.material contêm muitas classes com campos que aceitam expressões dinâmicas. Entre os exemplos estão os seguintes:

Para usar uma expressão dinâmica como um valor possível de um elemento no seu Bloco, use o tipo de propriedade dinâmica *Prop correspondente do elemento e transmita a fonte de dados para o método setDynamicValue() da classe de builder do tipo de propriedade dinâmica.

Os Blocos oferecem suporte a estes tipos de propriedade dinâmica:

Ao usar uma expressão dinâmica que afeta dimensões físicas (qualquer valor em um Bloco, exceto cor), também é necessário especificar um conjunto de restrições relacionadas, como um formato de string. Essas restrições permitem que o renderizador do sistema determine a quantidade máxima de espaço que um valor pode ocupar no Bloco. Normalmente, essas restrições são especificadas no nível do elemento, não no da expressão dinâmica, chamando um método que comece com setLayoutConstraintsForDynamic*.

O snippet de código abaixo mostra como exibir atualizações de frequência cardíaca usando três dígitos, com um valor substituto de --:

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

Usar um número pequeno de expressões em um único Bloco

O Wear OS limita o número de expressões que um único Bloco pode ter. Se um Bloco tiver muitas expressões dinâmicas no total, os valores dinâmicos serão ignorados, e o sistema voltará aos valores estáticos fornecidos aos respectivos tipos de propriedade dinâmica.

Você pode adicionar com segurança o conjunto de expressões abaixo a um Bloco, porque não há muitas expressões no total. Portanto, o Bloco se comporta corretamente:

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");

No entanto, este Bloco pode ter muitas expressões:

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

Consolidar dados dinâmicos em um objeto de estado

É possível consolidar o conjunto mais recente de atualizações das fontes de dados em um estado, que você transmite ao Bloco para renderizar o valor.

Para usar informações de estado nos seus Blocos, siga estas etapas:

  1. Estabeleça um conjunto de chaves que represente os diferentes valores do estado do Bloco. Este exemplo cria chaves para o consumo de água e uma observação:

    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");
    
  2. Na implementação de onTileRequest(), chame setState() e estabeleça mapeamentos iniciais de cada chave para um determinado valor de dados dinâmicos:

    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()
        );
    }
    
  3. Ao criar o layout, em um lugar em que você quer mostrar esses dados do estado, use um objeto do tipo Dynamic*. Você também pode chamar animate() para mostrar uma animação de transição do valor anterior para o atual:

    Kotlin

    DynamicInt32.from(KEY_WATER_INTAKE).animate()
    

    Java

    DynamicInt32.from(KEY_WATER_INTAKE).animate();
    
  4. Quando necessário, atualize também o estado com novos valores. Isso pode fazer parte da LoadAction de um Bloco.

    Neste exemplo, o valor de entrada de água é atualizado para 400:

    Kotlin

    state.addKeyToValueMapping(KEY_WATER_INTAKE,
            DynamicDataBuilders.DynamicDataValue.fromInt(400))
    

    Java

    state.addKeyToValueMapping(KEY_WATER_INTAKE,
            DynamicDataBuilders.DynamicDataValue.fromInt(400));