Apps foundation

Evolución de las tarjetas a widgets de Wear con Jetpack Glance y Remote Compose

Bocinas

  • François Deschênes

    Gerente de producto

  • Yuri Schimke

    Ingeniero de software

Acerca de

Acerca de la charla

En esta sesión, analizaremos en profundidad los aspectos técnicos de la evolución de las tarjetas de pantalla completa a los widgets de Wear dinámicos de altura parcial.

Exploraremos los beneficios técnicos de la API unificada de Jetpack Glance y el nuevo motor de Remote Compose, y detallaremos cómo se alinean sin problemas con Modern Android Development.

Por último, compartiremos la orientación más reciente sobre UX y analizaremos estrategias de migración prácticas para ayudarte a compilar la próxima generación de IU de un vistazo.

Resumen

  • Wear OS está evolucionando las tarjetas de pantalla completa a widgets de Wear creados con Jetpack Glance y potenciados por Remote Compose.
  • Remote Compose comparte el mismo compilador y entorno de ejecución que la IU de Jetpack Compose, a la vez que desvincula de forma segura la IU del proceso de la app.
  • Los tamaños de contenedores pequeños y grandes se alinean con los formatos de cuadrícula para dispositivos móviles, mientras que las APIs de Jetpack Glance Wear mantienen la retrocompatibilidad hasta Wear 4.
  • Para migrar las tarjetas existentes a widgets, se puede usar una estrategia de servicio dual recomendada o un solo servicio de compatibilidad.

Habilidades

Recursos

Gemini Notebook

Temas

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

Transcripción

Descargar transcripción

Introducción

  • Hola.

  • Me llamo Frank y dirijo nuestras experiencias de un vistazo para Wear OS.

  • Los relojes son compañeros esenciales para los usuarios durante todo el día. Hoy, te explicaremos cómo aprovechar la nueva experiencia de widgets de Wear.

  • Veremos dos formas de entregar las funciones más importantes directamente a la muñeca de los usuarios, y lo haremos con las APIs más recientes, que están diseñadas para que la compilación para Wear OS sea familiar e intuitiva para los desarrolladores de Android, como tú.

  • Hoy me acompaña en el escenario mi brillante colega y compañero de escritorio Yuri para analizar exactamente cómo lo estamos haciendo.

  • Hola, soy Yuri Schimke.

  • Soy ingeniero del equipo de Wear OS y mi enfoque principal es trabajar en las APIs de Remote Compose.

  • Hoy te daré una breve introducción a Remote Compose y también hablaré sobre el proceso para migrar de tarjetas a widgets de Wear.

  • Dicho esto, le cedo la palabra a Frank.

Evolución de las tarjetas a los widgets de Wear

  • Excelente.

  • Gracias, Yuri.

  • Oficialmente, llevaremos las muñecas al ecosistema unificado de widgets de Android, ya que convertiremos nuestras tarjetas de pantalla completa en widgets de Wear.

  • Y, como estas experiencias ahora se compilan con Jetpack Glance, puedes ver que es la misma infraestructura subyacente que se usa para los widgets para dispositivos móviles.

  • Verás que, en el futuro, te resultará aún más familiar compilar para Wear.

Descripción general de Remote Compose

  • Para potenciar estas experiencias, estamos evolucionando la pila de IU, la pila de IU subyacente.

  • Los widgets de Wear se basan en Remote Compose, un nuevo framework de Android para la IU remota.

  • Esto significa que, finalmente, puedes usar una API declarativa estrechamente alineada con Jetpack Compose para compilar tus experiencias fuera de la app, como los widgets.

  • Remote Compose ofrece gráficos modernos y diseños responsivos a los widgets, a la vez que garantiza el rendimiento y la eficiencia de la batería.

  • Además, al desacoplar la IU del proceso de la aplicación, los widgets se serializan de forma segura y se renderizan con facilidad en una amplia variedad de dispositivos Android.

  • Esto significa que puedes llevar estas experiencias enriquecidas a casi todas las plataformas remotas compatibles con un esfuerzo mínimo.

Tamaños de diseño y retrocompatibilidad

  • Para que la transición de las tarjetas a los widgets sea lo más fluida posible, presentamos dos tamaños de contenedores estándar: pequeño y grande.

  • Están diseñados para alinearse estrechamente con los formatos de widgets basados en cuadrículas de dos por uno y dos por dos a los que ya te acostumbraste cuando creabas widgets en dispositivos móviles.

  • A partir de Wear 7, en algunos fabricantes de hardware, tus widgets de Wear ahora se ubicarán en una bandeja de desplazamiento vertical junto con los widgets de otras apps.

  • Por ejemplo, así es como se ven los widgets de Wear en un Samsung Galaxy Watch.

  • La arquitectura de la plataforma está diseñada para admitir bandejas de desplazamiento vertical a medida que los fabricantes de hardware adopten este patrón en el futuro.

  • Y no tienes que preocuparte por dejar atrás a ningún usuario, ni a los existentes.

  • Las APIs de Jetpack Glance Wear están diseñadas para admitir la retrocompatibilidad hasta Wear 4.

  • En los dispositivos más antiguos que admiten una bandeja horizontal de widgets o de tarjetas, el sistema se encargará de eso por ti.

  • Solo debes indicar en tu código que quieres admitir el modo de pantalla completa, y el sistema tomará tu widget grande y lo mostrará como una tarjeta tradicional de pantalla completa en estos dispositivos.

  • Ahora, Yuri nos guiará por el código para mostrarte exactamente cómo migrar tus tarjetas existentes a widgets de Wear.

Estrategias de migración para las tarjetas existentes

  • Gracias, Frank.

  • Si estás creando un widget de Wear nuevo desde cero, es sencillo.

  • Solo debes definir un GlanceWearWidgetService y compilar tu IU con Remote Compose.

  • Sin embargo, si tu app ya tiene una tarjeta en producción, deberás migrar tus servicios, la IU y los datos.

  • Para que esta transición sea lo más fluida posible, proporcionamos rutas que garantizan una experiencia sin problemas para los usuarios existentes.

  • Para el servicio, puedes elegir entre dos estrategias: servicio doble o servicio único.

  • Con el servicio doble, que recomendamos, conservarás tu TileService existente para los dispositivos Wear OS más antiguos y agregarás un nuevo GlanceWearWidgetService para Wear OS 7 y versiones posteriores.

  • En cambio, con el servicio único, creas un nuevo GlanceWearWidgetService único que controla ambos protocolos y se ejecuta en un modo de compatibilidad para traducir tu widget en una tarjeta en dispositivos más antiguos.

  • De cualquier manera, seguirás llegando a tus usuarios.

  • Solo afecta el código que mantendrás en el futuro.

  • Además, acabamos de publicar documentación completa en el sitio para desarrolladores de Android en la que se detalla exactamente cómo y cuándo usar ambos patrones.

  • Consúltalo para obtener más información sobre qué estrategia es la mejor para tu app.

Cómo compilar la IU con Remote Compose

  • Ahora, hablemos de la capa de IU.

  • Cuando migras, tu modelo mental cambia.

  • Anteriormente, las tarjetas usaban ProtoLayout, una biblioteca compilada exclusivamente para tarjetas de Wear.

  • Por otro lado, los widgets de Wear se basan en Remote Compose.

  • Remote Compose comparte exactamente la misma base que la IU de Jetpack Compose, con el mismo compilador y entorno de ejecución principales.

  • Además, está diseñada para que resulte familiar, ya que se asemeja mucho a las bibliotecas de IU de Compose, como Foundation y Material 3.

  • Pero, en lugar de un cuadro, una columna o un texto, simplemente los cambiarás por RemoteBox, RemoteColumn o RemoteText.

  • En este caso, te familiarizarás con escribir mucho "remote".

  • Además, para acelerar tu flujo de trabajo, incluimos un conjunto completo de componentes de Remote Compose listos para usar diseñados específicamente para widgets.

  • Esto significa que tus diseños existentes de Wear Material 3, incluido lo que tienes en Figma, se pueden adaptar fácilmente a los widgets, lo que hace que esto sea familiar tanto para los ingenieros como para los diseñadores.

  • Para ver qué tan alineados están estos marcos, veamos un ejemplo concreto.

  • Crearemos nuestro propio componente de app sobre el botón de interruptor de Material 3.

  • A la izquierda, tenemos el componente estándar de Wear Compose que escribirías en tu app y, a la derecha, la versión de 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,
    )
  • Observa lo similares que son los patrones.

  • Sin embargo, cuando escribas código de Remote Compose, usarás la anotación RemoteComposable.

  • Y cuando usas parámetros conocidos, como modificadores y booleanos, estos tienen tipos equivalentes de Remote Compose, como RemoteModifier y RemoteBoolean.

  • Y usas acciones en lugar de lambdas.

  • En lugar de definir la ejecución del código que se ejecutará dentro de la lambda, deberás declarar una acción, como iniciar un intent pendiente, llamar a un servicio o actualizar un valor.

  • Pasemos al cuerpo de nuestro componente.

  • Notarás que incluso el tema está alineado.

  • Puedes aplicar tu esquema de colores de Material 3 al componente Remote Compose exactamente como lo harías en Compose estándar, lo que permite que tu widget coincida exactamente con el desarrollo de la marca de tu 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 último, veamos el resultado. La IU resultante es exactamente lo que esperarías: un botón de activación animado, responsivo y limpio.

Compatibilidad con la vista previa de Compose para widgets

  • Es difícil notar la diferencia.

  • Para ayudarte a diseñar, iterar y verificar los diseños de tus widgets sin tener que ejecutarlos siempre en un dispositivo o emulador, agregamos compatibilidad con la vista previa de Compose para los widgets de Wear.

  • Como los widgets dependen de las restricciones de contenedores específicas del OEM, como el tamaño, la forma, el padding y el radio de las esquinas, compilamos sobre la anotación de vista previa estándar, pero con un proveedor de parámetros adicional para insertar estas configuraciones.

    @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)
  • La biblioteca proporciona implementaciones de proveedores de PreviewParameter preconfiguradas que coinciden con dispositivos Wear OS reales.

  • Estas anotaciones de vista previa te permiten verificar fácilmente tu diseño en diferentes formas de hardware.

  • Además de probar tus diseños, puedes usar estas vistas previas para generar las imágenes estáticas que se requieren para el selector de widgets integrado en el 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)
  • La biblioteca proporciona un parámetro para la forma de vista previa rectangular que emula los contenedores rectangulares completos sin recortar.

Herramientas de implementación y migración de muestra

  • Y si quieres ver cómo funciona exactamente en la práctica, actualizamos nuestra app de ejemplo de contenido multimedia de Horologist para incluir un widget nuevo que muestra esta API.

  • Mostramos cómo agregar las versiones pequeña y grande del widget de contenido multimedia.

  • Consulta el repositorio en GitHub para ver una comparación en paralelo del código heredado de ProtoLayout y la nueva implementación de Remote Compose.

  • Estamos seguros de que te gustará.

  • Por último, esta no sería una charla completa sin mencionar la IA.

  • Para acelerar la migración, publicamos una nueva habilidad de migración de tarjetas a widgets en GitHub.

  • Puedes usarla con el agente que elijas o agregarla directamente a Android Studio con la CLI de Android.

    android skills add wear-widgets
    android skills add --all
  • Ahora, François concluirá la presentación.

Resumen y conclusión

  • Gracias.

  • Gracias, Yuri.

  • Como puedes ver, convertir tus tarjetas en widgets de Wear es un proceso muy sencillo.

  • En resumen, cuando migras a Jetpack Glance Wear y Remote Compose, no solo actualizas tu IU, sino que te unes a un ecosistema de widgets de Android más unificado.

  • Ahora puedes compilar widgets atractivos y retrocompatibles con un framework moderno y declarativo que está completamente alineado con Jetpack Compose.

  • Puedes comenzar a migrar tus tarjetas hoy mismo. Para ello, consulta la guía de introducción en el sitio de Android Developers y obtén todos los detalles.

  • Esperamos ver todas las experiencias expresivas y fáciles de consultar que crees para la muñeca.

  • Muchas gracias por acompañarnos hoy, esta mañana, y esperamos que disfruten el resto de la conferencia.

  • Gracias.

Código

Fragmentos de código

Fragmento de Kotlin en el minuto 06:40 de "Cómo compilar una IU con Remote Compose"
// Wear Compose
@Composable
fun HistoryEraserButton(
  onClick: () -> Unit,
  modifier: Modifier = Modifier,
  checked: Boolean = true,
  onCheckedChange: (Boolean) -> Unit = {},
)
Fragmento de Kotlin en el minuto 6:40 de "Cómo compilar una IU con Remote Compose"
// Wear RemoteCompose
@RemoteComposable
@Composable
fun RemoteHistoryEraserButton(
  onClick: Action,
  modifier: RemoteModifier = RemoteModifier,
  checked: RemoteBoolean = true.rb,
  onCheckedChange: Action = Action.Empty,
)
Fragmento de Kotlin en el minuto 07:39 de "Cómo compilar una IU con 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!") },
  )
Fragmento de Kotlin en el minuto 07:39 de "Cómo compilar una IU con Remote Compose"
// 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) },
  )
Fragmento de Kotlin en el minuto 8:20 de "Compatibilidad de la vista previa de Compose con 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)
Fragmento de Kotlin en el minuto 8:58 de "Compatibilidad de la vista previa de Compose con 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)
Fragmento de shell en el minuto 09:58 de "Sample implementation and migration tools"
android skills add wear-widgets
android skills add --all