Apps foundation

Evolução dos blocos para widgets do Wear usando o Jetpack Glance e o Remote Compose

Alto-falantes

  • François Deschênes

    Gerente de produtos

  • Yuri Schimke

    Engenheiro de software

Sobre

Sobre a palestra

Nesta sessão, faremos uma investigação técnica da evolução dos blocos de tela cheia para os widgets dinâmicos de altura parcial do Wear.

Vamos analisar os benefícios técnicos da API unificada do Jetpack Glance e do novo mecanismo do Remote Compose, detalhando como eles se alinham perfeitamente ao Modern Android Development.

Por fim, vamos compartilhar as orientações mais recentes de UX e mostrar estratégias práticas de migração para ajudar você a criar a próxima geração de interfaces de fácil visualização.

Resumo

  • O Wear OS está transformando os blocos de tela cheia em widgets do Wear criados com o Jetpack Glance e com tecnologia do Remote Compose.
  • O Remote Compose compartilha o mesmo compilador e ambiente de execução que a interface do Jetpack Compose, ao mesmo tempo que separa com segurança a interface do processo do app.
  • Os tamanhos de contêineres pequenos e grandes se alinham aos formatos de grade para dispositivos móveis, enquanto as APIs Jetpack Glance Wear mantêm a compatibilidade com versões anteriores até o Wear 4.
  • A migração de blocos para widgets pode usar uma estratégia recomendada de serviço duplo ou um único serviço de compatibilidade.

Habilidades

Recursos

Gemini Notebook

Tópicos

  • wear-os
  • wear-widgets
  • jetpack-glance
  • remote-compose
  • tiles-migration
  • jetpack-compose

Transcrição

Introdução

  • Olá!

  • Meu nome é Frank e eu lidero as experiências de visualização rápida do Wear OS.

  • Os relógios são companheiros essenciais para os usuários durante todo o dia. Hoje, vamos mostrar como aproveitar a nova experiência dos widgets do Wear.

  • Vamos analisar duas maneiras de oferecer os recursos mais importantes diretamente no pulso dos usuários, usando as APIs mais recentes, que foram projetadas para que a criação para o Wear OS seja familiar e intuitiva para desenvolvedores Android, como você.

  • Para mostrar exatamente como estamos fazendo isso, convido ao palco meu brilhante colega e companheiro de mesa, Yuri.

  • Olá, meu nome é Yuri Schimke.

  • Sou engenheiro na equipe do Wear OS e meu foco principal é trabalhar nas APIs Remote Compose.

  • Hoje, vou apresentar brevemente o Remote Compose e falar sobre o processo de migração de blocos para widgets do Wear.

  • Agora, vou passar a palavra de volta para o Frank.

Evolução dos blocos para widgets do Wear

  • Ótimo.

  • Obrigado, Yuri.

  • Estamos trazendo oficialmente o pulso para o ecossistema unificado de widgets do Android, transformando nossos blocos de tela cheia em widgets do Wear.

  • Como essas experiências agora são criadas usando o Jetpack Glance, você pode ver que é a mesma infraestrutura usada para widgets de dispositivos móveis.

  • Você vai perceber que criar para o Wear vai ficar cada vez mais familiar no futuro.

Visão geral do Compose Remoto

  • Para potencializar essas experiências, estamos evoluindo a pilha de interface, que é a base de tudo.

  • Os widgets do Wear usam o Remote Compose, um novo framework do Android para interfaces remotas.

  • Isso significa que você pode usar uma API declarativa alinhada ao Jetpack Compose para criar experiências fora do app, como widgets.

  • O Remote Compose traz gráficos modernos e layouts responsivos para widgets, garantindo desempenho e eficiência de bateria.

  • E, ao separar a interface do processo do aplicativo, os widgets são serializados com segurança e renderizados com facilidade em uma ampla variedade de dispositivos Android.

  • Isso significa que você pode levar essas experiências ricas para praticamente todas as plataformas remotas compatíveis com o mínimo de esforço.

Tamanhos de layout e compatibilidade com versões anteriores

  • Para facilitar ao máximo a transição dos blocos para os widgets, estamos lançando dois tamanhos de contêiner padrão: pequeno e grande.

  • Eles foram projetados para se alinhar aos formatos de widget baseados em grade de 2x1 e 2x2 que você já conhece ao criar widgets em dispositivos móveis.

  • E, a partir do Wear 7, em alguns fabricantes de hardware, os widgets do Wear vão ficar em uma bandeja de rolagem vertical ao lado dos widgets de outros apps.

  • Por exemplo, é assim que os widgets do Wear aparecem em um Samsung Galaxy Watch.

  • A arquitetura da plataforma foi criada para oferecer suporte a bandejas de rolagem vertical, já que os fabricantes de hardware vão adotar esse padrão no futuro.

  • E você não precisa se preocupar em deixar nenhum usuário, ou usuários atuais, para trás.

  • As APIs Jetpack Glance Wear foram projetadas para oferecer compatibilidade com versões anteriores até o Wear 4.

  • Em dispositivos mais antigos que oferecem suporte a uma bandeja horizontal de widgets ou blocos, o sistema vai cuidar disso para você.

  • Basta declarar no código que você quer oferecer suporte ao modo de tela cheia, e o sistema vai pegar o widget grande e mostrá-lo como um bloco tradicional em tela cheia nesses dispositivos.

  • Agora, Yuri vai mostrar o código para que você entenda exatamente como migrar seus blocos para os widgets do Wear.

Estratégias de migração para blocos atuais

  • Obrigado, Frank.

  • Se você estiver criando um novo widget do Wear do zero, é simples.

  • Basta definir um GlanceWearWidgetService e criar sua interface usando o Remote Compose.

  • No entanto, se o app já tiver um bloco em produção, será necessário migrar os serviços, a interface e os dados.

  • Para que essa transição seja o mais tranquila possível, oferecemos caminhos para garantir uma experiência sem interrupções para os usuários atuais.

  • Para o serviço, é possível escolher entre duas estratégias: serviço duplo ou único.

  • Com o serviço duplo, que recomendamos, você mantém o TileService atual para dispositivos Wear OS mais antigos e adiciona um novo GlanceWearWidgetService para o Wear OS 7+.

  • Por outro lado, com o serviço único, você cria um novo GlanceWearWidgetService que processa os dois protocolos, sendo executado em um modo de compatibilidade para traduzir o widget em um bloco em dispositivos mais antigos.

  • De qualquer forma, você vai continuar alcançando seus usuários.

  • Isso só afeta o código que você vai manter daqui para frente.

  • E acabamos de publicar uma documentação abrangente no site para desenvolvedores Android detalhando exatamente como e quando usar esses dois padrões.

  • Confira para saber qual estratégia é melhor para seu app.

Como criar uma interface com o Remote Compose

  • Agora vamos falar sobre a camada de interface.

  • Ao migrar, seu modelo mental muda.

  • Antes, os blocos usavam o ProtoLayout, uma biblioteca criada exclusivamente para blocos do Wear.

  • Já os widgets do Wear são baseados no Remote Compose.

  • O Remote Compose compartilha exatamente a mesma base que a interface do Jetpack Compose, usando o mesmo compilador e ambiente de execução principais.

  • Ele foi projetado para ser familiar, correspondendo de perto às bibliotecas de interface do Compose, como Foundation e Material 3.

  • Mas, em vez de uma caixa, coluna ou texto, basta trocar por RemoteBox, RemoteColumn ou RemoteText.

  • Nesse caso, você vai se acostumar a escrever muito "remote".

  • Além disso, para acelerar seu fluxo de trabalho, incluímos um conjunto completo de componentes do Remote Compose prontos para uso, projetados especificamente para widgets.

  • Isso significa que seus designs atuais do Wear Material 3, incluindo o que você tem no Figma, podem ser facilmente adaptados para widgets, o que é familiar para engenheiros e designers.

  • Para entender como esses frameworks estão alinhados, vamos analisar um exemplo concreto.

  • Vamos criar nosso próprio componente de app com base no botão de alternância do Material 3.

  • À esquerda, temos o componente padrão do Wear Compose que você escreveria no app, e à direita está a versão do Remote Compose.

    // Wear Compose
    @Composable
    fun HistoryEraserButton(
      onClick: () -> Unit,
      modifier: Modifier = Modifier,
      checked: Boolean = true,
      onCheckedChange: (Boolean) -> Unit = {},
    )
    // Wear RemoteCompose
    @RemoteComposable
    @Composable
    fun RemoteHistoryEraserButton(
      onClick: Action,
      modifier: RemoteModifier = RemoteModifier,
      checked: RemoteBoolean = true.rb,
      onCheckedChange: Action = Action.Empty,
    )
  • Observe como os padrões são semelhantes.

  • No entanto, ao escrever o código do Remote Compose, você vai usar a anotação RemoteComposable.

  • E, quando você usa parâmetros conhecidos, como modificador e booleano, eles têm tipos equivalentes do Remote Compose, como RemoteModifier e RemoteBoolean.

  • E você usa ações em vez de lambdas.

  • Em vez de definir o código a ser executado dentro do lambda, você precisa declarar uma ação, como iniciar uma intent pendente, chamar um serviço ou atualizar um valor.

  • Vamos para o corpo do componente.

  • Até o tema está alinhado.

  • É possível aplicar o esquema de cores do Material 3 ao componente do Remote Compose exatamente como você faria no Compose padrão, o que permite que o widget corresponda exatamente à marca do seu app.

    // Wear Compose
      val colors = MaterialTheme.colorScheme
      SplitSwitchButton(
        checked = checked,
        onCheckedChange = onCheckedChange,
        toggleContentDescription = "Arm Eraser",
        onContainerClick = onClick,
        modifier = modifier.fillMaxWidth(),
        colors =
          SwitchButtonDefaults.splitSwitchButtonColors(
            checkedContainerColor = colors.errorContainer, …
          ),
        label = { Text("History Eraser") },
        secondaryLabel = { Text("Don't touch it!") },
      )
    // Wear RemoteCompose
      val colors = RemoteMaterialTheme.colorScheme
      RemoteSplitSwitchButton(
        checked = checked,
        onCheckedChange = onCheckedChange,
        toggleContentDescription = "Arm Eraser".rs,
        onContainerClick = onClick,
        modifier = modifier.fillMaxWidth(),
        colors =
          RemoteSplitSwitchButtonDefaults.splitSwitchButtonColors(
            checkedContainerColor = colors.errorContainer, …
          ),
        label = { RemoteText("History Eraser".rs) },
        secondaryLabel = { RemoteText("Don't touch it!".rs) },
      )
  • Por fim, vamos ver o resultado. A interface resultante é exatamente o que você espera: um botão ativar/desativar limpo, responsivo e animado.

Suporte à visualização do Compose para widgets

  • É difícil perceber a diferença.

  • Para ajudar você a projetar, iterar e verificar os layouts de widgets sem precisar sempre executar em um dispositivo ou emulador, adicionamos suporte à visualização do Compose para widgets do Wear.

  • Como os widgets dependem de restrições de contêiner específicas do OEM, como tamanho, formato, padding e raio de canto, criamos com base na anotação de prévia padrão, mas com um provedor de parâmetros extra para injetar essas configurações.

    @Preview(name = "Squircle Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
    @Composable
    fun WeatherWidgetSquirclePreview(
        @PreviewParameter(SquircleAllWidgetPreviewParams::class) params: WearWidgetParams
    ) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
    
    @Preview(name = "Round Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
    @Composable
    fun WeatherWidgetRoundPreview(
        @PreviewParameter(RoundAllWidgetPreviewParams::class) params: WearWidgetParams
    ) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
  • A biblioteca oferece implementações pré-configuradas de provedores PreviewParameter que correspondem a dispositivos Wear OS reais.

  • Essas anotações de visualização permitem verificar facilmente o layout em vários formatos de hardware diferentes.

  • Além de testar seus layouts, você pode usar essas prévias para gerar as imagens estáticas necessárias para o seletor de widgets no dispositivo.

    @Preview(name = "Widget Picker Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
    @Composable
    fun WeatherWidgetRectangularPreview(
        @PreviewParameter(RectangularAllWidgetPreviewParams::class) params: WearWidgetParams
    ) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
  • A biblioteca fornece um parâmetro para a forma de visualização retangular que emula os contêineres retangulares completos sem cortes.

Exemplos de ferramentas de implementação e migração

  • E se você quiser ver como isso funciona na prática, atualizamos nosso app de exemplo de mídia do Horologist para incluir um novo widget que mostra essa API.

  • Mostramos como adicionar as versões pequena e grande do widget de mídia.

  • Confira no GitHub uma comparação lado a lado do código legado do ProtoLayout e da nova implementação do Remote Compose.

  • Temos certeza de que você vai gostar.

  • Por fim, não seria uma palestra sem mencionar a IA.

  • Para acelerar sua migração, publicamos uma nova habilidade de migração de blocos para widgets no GitHub.

  • Você pode usá-la com o agente de sua escolha ou adicioná-la diretamente ao Android Studio usando a CLI do Android.

    android skills add wear-widgets
    android skills add --all
  • Agora, vou passar a palavra para o François, que vai encerrar o assunto.

Resumo e conclusão

  • Nossa equipe agradece.

  • Obrigado, Yuri.

  • Como você pode ver, transformar seus blocos em widgets do Wear é um processo muito simples.

  • Ao migrar para o Jetpack Glance Wear e o Remote Compose, você não está apenas atualizando sua interface, mas também se juntando a um ecossistema de widgets do Android mais unificado.

  • Agora você pode criar widgets bonitos e compatíveis com versões anteriores usando um framework moderno e declarativo totalmente alinhado ao Jetpack Compose.

  • Você pode começar a migrar seus blocos hoje mesmo. Acesse o guia de iniciação no site para desenvolvedores Android para conferir todos os detalhes.

  • Estamos ansiosos para ver todas as experiências expressivas e fáceis de ler que você vai criar para o pulso.

  • Agradecemos muito pela participação de vocês hoje, nesta manhã, e esperamos que aproveitem o restante da conferência.

  • Nossa equipe agradece.

Código

Snippets de código

Snippet do Kotlin em 06:40 em "Como criar uma interface com o Remote Compose"
// Wear Compose
@Composable
fun HistoryEraserButton(
  onClick: () -> Unit,
  modifier: Modifier = Modifier,
  checked: Boolean = true,
  onCheckedChange: (Boolean) -> Unit = {},
)
Snippet do Kotlin em 06:40 em "Como criar uma interface com o Remote Compose"
// Wear RemoteCompose
@RemoteComposable
@Composable
fun RemoteHistoryEraserButton(
  onClick: Action,
  modifier: RemoteModifier = RemoteModifier,
  checked: RemoteBoolean = true.rb,
  onCheckedChange: Action = Action.Empty,
)
Snippet em Kotlin aos 07:39 em "Como criar uma interface com o Remote Compose"
// Wear Compose
  val colors = MaterialTheme.colorScheme
  SplitSwitchButton(
    checked = checked,
    onCheckedChange = onCheckedChange,
    toggleContentDescription = "Arm Eraser",
    onContainerClick = onClick,
    modifier = modifier.fillMaxWidth(),
    colors =
      SwitchButtonDefaults.splitSwitchButtonColors(
        checkedContainerColor = colors.errorContainer, …
      ),
    label = { Text("History Eraser") },
    secondaryLabel = { Text("Don't touch it!") },
  )
Snippet do Kotlin em 07:39 em "Como criar uma interface com o Compose remoto"
// Wear RemoteCompose
  val colors = RemoteMaterialTheme.colorScheme
  RemoteSplitSwitchButton(
    checked = checked,
    onCheckedChange = onCheckedChange,
    toggleContentDescription = "Arm Eraser".rs,
    onContainerClick = onClick,
    modifier = modifier.fillMaxWidth(),
    colors =
      RemoteSplitSwitchButtonDefaults.splitSwitchButtonColors(
        checkedContainerColor = colors.errorContainer, …
      ),
    label = { RemoteText("History Eraser".rs) },
    secondaryLabel = { RemoteText("Don't touch it!".rs) },
  )
Snippet do Kotlin em 08:20 em "Suporte à visualização do Compose para widgets"
@Preview(name = "Squircle Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetSquirclePreview(
    @PreviewParameter(SquircleAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)

@Preview(name = "Round Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetRoundPreview(
    @PreviewParameter(RoundAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
Snippet do Kotlin em 08:58 em "Suporte à visualização do Compose para widgets"
@Preview(name = "Widget Picker Preview", device = "spec:width=1000dp,height=1000dp,dpi=320")
@Composable
fun WeatherWidgetRectangularPreview(
    @PreviewParameter(RectangularAllWidgetPreviewParams::class) params: WearWidgetParams
) = WearWidgetPreview(MockWeatherWidget(65, WeatherCondition.CLOUDY), params)
Snippet de shell em 09:58 em "Exemplo de implementação e ferramentas de migração"
android skills add wear-widgets
android skills add --all