Control de versiones de tarjetas

En los dispositivos Wear OS, dos componentes clave renderizan tarjetas con control de versiones independiente. Para garantizar que las tarjetas de tus apps funcionen correctamente en todos los dispositivos, es importante comprender esta arquitectura subyacente.

  • Bibliotecas relacionadas con tarjetas de Jetpack: Estas bibliotecas (incluidas las tarjetas de Wear y Wear ProtoLayout) están incorporadas en tu app y, como desarrollador, tú controlas sus versiones. Tu app usa estas bibliotecas para crear un objeto TileBuilder.Tile (la estructura de datos que representa tu tarjeta) en respuesta a la llamada onTileRequest() del sistema.
  • Renderizador de ProtoLayout: Este componente del sistema es responsable de renderizar el objeto Tile en la pantalla y controlar las interacciones del usuario. El desarrollador de la app no controla la versión del renderizador y puede variar entre los dispositivos, incluso aquellos con hardware idéntico.

La apariencia o el comportamiento de una tarjeta pueden variar según las versiones de la biblioteca de tarjetas de Jetpack de tu app y la versión del renderizador de ProtoLayout en el dispositivo del usuario. Por ejemplo, un dispositivo puede admitir la rotación o la visualización de datos de frecuencia cardíaca, y otro no.

En este documento, se explica cómo garantizar que tu app sea compatible con diferentes versiones de la biblioteca de tarjetas y el renderizador de ProtoLayout, y cómo migrar a versiones posteriores de la biblioteca de Jetpack.

Ten en cuenta la compatibilidad

Para crear una tarjeta que funcione correctamente en una variedad de dispositivos, debes tener en cuenta lo siguiente.

Cómo detectar la versión del renderizador

  • Usa el método getRendererSchemaVersion() del objeto DeviceParameters que se pasa a tu método onTileRequest(). Este método muestra los números de versión principal y secundaria del renderizador de ProtoLayout en el dispositivo.
  • Luego, puedes usar la lógica condicional en tu implementación de onTileRequest() para adaptar el diseño o el comportamiento de tu tarjeta según la versión del renderizador detectada.
    • Por ejemplo, si no se admite una animación específica, puedes mostrar una imagen estática.

La anotación @RequiresSchemaVersion

  • La anotación @RequiresSchemaVersion en los métodos de ProtoLayout indica la versión mínima del esquema del renderizador necesaria para que ese método se comporte como se documenta (ejemplo).
    • Si bien llamar a un método que requiera una versión de renderizador superior a la que está disponible en el dispositivo no hará que tu app falle, podría provocar que no se muestre el contenido o que se ignore la función.

Ejemplo

override fun onTileRequest(
    requestParams: TileService.TileRequest
): ListenableFuture<Tile> {
    val rendererVersion =
        requestParams.deviceConfiguration.rendererSchemaVersion
    val tile = Tile.Builder()

    if (
        rendererVersion.major > 1 ||
            (rendererVersion.major == 1 && rendererVersion.minor >= 300)
    ) {
        // Use a feature supported in renderer version 1.300 or later
        tile.setTileTimeline(/* ... */ )
    } else {
        // Provide fallback content for older renderers
        tile.setTileTimeline(/* ... */ )
    }

    return Futures.immediateFuture(tile.build())
}

Prueba con diferentes versiones del renderizador

Para probar tus tarjetas con diferentes versiones del renderizador, impleméntalas en diferentes versiones del emulador de Wear OS. (En dispositivos físicos, Play Store o las actualizaciones del sistema entregan las actualizaciones del renderizador de ProtoLayout). No es posible forzar la instalación de una versión específica del renderizador).

La función Tile Preview de Android Studio usa un renderizador incorporado en la biblioteca de Jetpack ProtoLayout de la que depende tu código, por lo que otro enfoque es depender de diferentes versiones de la biblioteca de Jetpack cuando se prueban tarjetas.

Cómo migrar a Tiles 1.5 o ProtoLayout 1.3 (Material 3 expresivo)

Actualiza tus bibliotecas de tarjetas de Jetpack para aprovechar las mejoras más recientes, incluidos los cambios en la IU para que tus tarjetas se integren sin problemas con el sistema.

Jetpack Tiles 1.5 y Jetpack ProtoLayout 1.3 presentan varios cambios y mejoras notables. Por ejemplo:

  • Una API similar a Compose para describir la IU.
  • Componentes Material 3 expresivos, incluido el botón de borde inferior y la compatibilidad con elementos visuales mejorados: animaciones de Lottie, más tipos de gradientes y nuevos estilos de línea de arco. - Nota: Algunas de estas funciones también se pueden usar sin migrar a la API nueva.

Recomendaciones

  • Migra todas tus tarjetas de forma simultánea. Evita mezclar versiones de tarjetas dentro de tu app. Si bien los componentes de Material 3 residen en un artefacto independiente (androidx.wear.protolayout:protolayout-material3), lo que hace que sea técnicamente posible usar tarjetas de M2.5 y M3 en la misma app, te recomendamos que no uses este enfoque, a menos que sea absolutamente necesario (por ejemplo, si tu app tiene una gran cantidad de tarjetas que no se pueden migrar todas a la vez).
  • Adopta la guía de UX de tarjetas. Dada la naturaleza altamente estructurada y con plantillas de las tarjetas, usa los diseños de los ejemplos existentes como puntos de partida para tus propios diseños.
  • Realiza pruebas en una variedad de tamaños de pantalla y fuente. A menudo, las tarjetas contienen mucha información, lo que hace que el texto (especialmente cuando se coloca en botones) sea susceptible de desbordarse y recortarse. Para minimizar esto, usa los componentes previamente compilados y evita la personalización extensa. Realiza pruebas con la función de vista previa de tarjetas de Android Studio y en varios dispositivos reales.

Proceso de migración

Actualiza las dependencias

Primero, actualiza tu archivo build.gradle.kts. Actualiza las versiones y cambia la dependencia protolayout-material a protolayout-material3, como se muestra a continuación:

// In build.gradle.kts

//val tilesVersion = "1.4.1"
//val protoLayoutVersion = "1.2.1"

// Use these versions for M3.
val tilesVersion = "1.5.0-rc01"
val protoLayoutVersion = "1.3.0-rc01"

 dependencies {
     // Use to implement support for wear tiles
     implementation("androidx.wear.tiles:tiles:$tilesVersion")

     // Use to utilize standard components and layouts in your tiles
     implementation("androidx.wear.protolayout:protolayout:$protoLayoutVersion")

     // Use to utilize components and layouts with Material Design in your tiles
     // implementation("androidx.wear.protolayout:protolayout-material:$protoLayoutVersion")
     implementation("androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion")

     // Use to include dynamic expressions in your tiles
     implementation("androidx.wear.protolayout:protolayout-expression:$protoLayoutVersion")

     // Use to preview wear tiles in your own app
     debugImplementation("androidx.wear.tiles:tiles-renderer:$tilesVersion")

     // Use to fetch tiles from a tile provider in your tests
     testImplementation("androidx.wear.tiles:tiles-testing:$tilesVersion")
 }

TileService no sufre cambios significativos.

Los cambios principales de esta migración afectan a los componentes de la IU. En consecuencia, tu implementación de TileService, incluidos los mecanismos de carga de recursos, debería requerir modificaciones mínimas o ninguna.

La excepción principal incluye el seguimiento de actividades de tarjetas: si tu app usa onTileEnterEvent() o onTileLeaveEvent(), debes migrar a onRecentInteractionEventsAsync(). A partir de la API 36, estos eventos se agruparán.

Adapta tu código de generación de diseño

En ProtoLayout 1.2 (M2.5), el método onTileRequest() muestra un TileBuilders.Tile. Este objeto contenía varios elementos, incluido un TimelineBuilders.Timeline, que a su vez contenía el LayoutElement que describía la IU de la tarjeta.

Con ProtoLayout 1.3 (M3), si bien la estructura y el flujo de datos generales no cambiaron, LayoutElement ahora se construye con un enfoque inspirado en Compose con un diseño basado en slots definidos que son (de arriba abajo) titleSlot (por lo general, para un título o encabezado principal), mainSlot (para el contenido principal) y bottomSlot (a menudo, para acciones como un botón de borde o información complementaria, como texto breve). La función primaryLayout() construye este diseño.

El diseño de una tarjeta que muestra mainSlot, titleSlot y bottomSlot
Figura 1.: Son los espacios de una tarjeta.
Comparación de las funciones de diseño de M2.5 y M3

M2.5

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters
) =
    PrimaryLayout.Builder(deviceConfiguration)
        .setResponsiveContentInsetEnabled(true)
        .setContent(
            Text.Builder(context, "Hello World!")
                .setTypography(Typography.TYPOGRAPHY_BODY1)
                .setColor(argb(0xFFFFFFFF.toInt()))
                .build()
        )
        .build()

M3

fun myLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
) =
    materialScope(context, deviceConfiguration) {
        primaryLayout(mainSlot = { text("Hello, World!".layoutString) })
    }

Para destacar las diferencias clave, haz lo siguiente:

  1. Eliminación de Builders. El patrón de generador tradicional para los componentes de la IU de Material3 se reemplaza por una sintaxis más declarativa inspirada en Compose. (Los componentes que no son de la IU, como String/Color/Modifiers, también obtienen nuevos wrappers de Kotlin).
  2. Funciones de diseño y de inicialización estandarizadas. Los diseños de M3 se basan en funciones de inicialización y estructura estandarizadas: materialScope() y primaryLayout(). Estas funciones obligatorias inicializan el entorno de M3 (temas, alcance de componentes a través de materialScope) y definen el diseño principal basado en el espacio (a través de primaryLayout). Se debe llamar a ambos exactamente una vez por diseño.

Temas

Color

Una función destacada de Material 3 Expressive son los "temas dinámicos": las tarjetas que habilitan esta función (activada de forma predeterminada) se mostrarán en el tema proporcionado por el sistema (la disponibilidad depende del dispositivo y la configuración del usuario).

Otro cambio en M3 es una expansión de la cantidad de tokens de color, que aumentó de 4 a 29. Los nuevos tokens de color se pueden encontrar en la clase ColorScheme.

Tipografía

Al igual que M2.5, M3 depende en gran medida de constantes de tamaño de fuente predefinidas. No se recomienda especificar directamente un tamaño de fuente. Estas constantes se encuentran en la clase Typography y ofrecen un rango ligeramente expandido de opciones más expresivas.

Para obtener información detallada, consulta la documentación de Tipografía.

Forma

La mayoría de los componentes de M3 pueden variar en la dimensión de la forma y el color.

Un textButton (en mainSlot) con la forma full:

Tarjeta con forma &quot;completa&quot; (esquinas más redondeadas)
Figura 2:: Mosaico con forma "completa"

El mismo textButton con la forma small:

Teja con forma &quot;pequeña&quot; (menos esquinas redondeadas)
Figura 3:: Mosaico con forma "pequeña"

Componentes

Los componentes de M3 son mucho más flexibles y configurables que sus contrapartes de M2.5. Mientras que el nivel M2.5 a menudo requiere componentes distintos para tratamientos visuales variados, el nivel M3 suele emplear un componente “base” generalizado pero altamente configurable con buenos valores predeterminados.

Este principio se aplica al diseño "raiz". En M2.5, era un PrimaryLayout o un EdgeContentLayout. En M3, después de establecer un solo MaterialScope de nivel superior, se llama a la función primaryLayout(). Esto muestra el diseño raíz directamente (no se necesitan compiladores) y acepta LayoutElements para varias "ranuras", como titleSlot, mainSlot y bottomSlot. Estos espacios se pueden propagar con componentes de IU concretos, como los que devuelven text(), button() o card(), o estructuras de diseño, como Row o Column de LayoutElementBuilders.

Los temas representan otra mejora clave de la M3. De forma predeterminada, los elementos de la IU se adhieren automáticamente a las especificaciones de diseño de M3 y admiten temas dinámicos.

M2.5 M3
Elementos interactivos
Button o Chip
Texto
Text text()
Indicadores de progreso
CircularProgressIndicator circularProgressIndicator() o segmentedCircularProgressIndicator()
Diseño
PrimaryLayout o EdgeContentLayout primaryLayout()
buttonGroup()
Imágenes
icon(), avatarImage() o backgroundImage()

Modificadores

En M3, Modifiers, que usas para decorar o aumentar un componente, se parecen más a Compose. Este cambio puede reducir el texto de referencia, ya que construye automáticamente los tipos internos adecuados. (Este cambio es ortogonal al uso de componentes de la IU de M3. Si es necesario, puedes usar modificadores de estilo de compilador de ProtoLayout 1.2 con componentes de la IU de M3 y viceversa).

M2.5

// A Builder-style modifier to set the opacity of an element to 0.5
fun myModifier(): ModifiersBuilders.Modifiers =
    ModifiersBuilders.Modifiers.Builder()
        .setOpacity(TypeBuilders.FloatProp.Builder(0.5F).build())
        .build()

M3

// The equivalent Compose-like modifier is much simpler
fun myModifier(): LayoutModifier = LayoutModifier.opacity(0.5F)

Puedes construir modificadores con cualquier estilo de API y también puedes usar la función de extensión toProtoLayoutModifiers() para convertir un LayoutModifier en un ModifiersBuilders.Modifier.

Funciones auxiliares

Si bien ProtoLayout 1.3 permite que muchos componentes de la IU se expresen con una API inspirada en Compose, los elementos de diseño básicos, como las filas y las columnas de LayoutElementBuilders, siguen usando el patrón de generador. Para cerrar esta brecha estilística y promover la coherencia con las nuevas APIs de componentes de M3, considera usar funciones auxiliares.

Sin ayuda

primaryLayout(
    mainSlot = {
        LayoutElementBuilders.Column.Builder()
            .setWidth(expand())
            .setHeight(expand())
            .addContent(text("A".layoutString))
            .addContent(text("B".layoutString))
            .addContent(text("C".layoutString))
            .build()
    }
)

Con ayudantes

// Function literal with receiver helper function
fun column(builder: Column.Builder.() -> Unit) =
    Column.Builder().apply(builder).build()

primaryLayout(
    mainSlot = {
        column {
            setWidth(expand())
            setHeight(expand())
            addContent(text("A".layoutString))
            addContent(text("B".layoutString))
            addContent(text("C".layoutString))
        }
    }
)

Cómo migrar a Tiles 1.2 o ProtoLayout 1.0

A partir de la versión 1.2, la mayoría de las APIs de diseño de tarjetas está en el espacio de nombres androidx.wear.protolayout. Para usar las APIs más recientes, completa los siguientes pasos de migración en tu código.

Actualiza las dependencias

En el archivo de compilación del módulo de tu app, realiza los siguientes cambios:

Groovy

  // Remove
  implementation 'androidx.wear.tiles:tiles-material:version'

  // Include additional dependencies
  implementation "androidx.wear.protolayout:protolayout:1.2.1"
  implementation "androidx.wear.protolayout:protolayout-material:1.2.1"
  implementation "androidx.wear.protolayout:protolayout-expression:1.2.1"

  // Update
  implementation "androidx.wear.tiles:tiles:1.4.1"

Kotlin

  // Remove
  implementation("androidx.wear.tiles:tiles-material:version")

  // Include additional dependencies
  implementation("androidx.wear.protolayout:protolayout:1.2.1")
  implementation("androidx.wear.protolayout:protolayout-material:1.2.1")
  implementation("androidx.wear.protolayout:protolayout-expression:1.2.1")

  // Update
  implementation("androidx.wear.tiles:tiles:1.4.1")

Actualiza los espacios de nombres

En los archivos de código basados en Kotlin y Java de tu app, realiza las siguientes actualizaciones. También puedes ejecutar esta secuencia de comandos de cambio de nombres de los espacios de nombres.

  1. Reemplaza todas las importaciones de androidx.wear.tiles.material.* por androidx.wear.protolayout.material.*. Realiza también este paso para la biblioteca androidx.wear.tiles.material.layouts.
  2. Reemplaza la mayoría de las demás importaciones de androidx.wear.tiles.* por androidx.wear.protolayout.*.

    Las importaciones de androidx.wear.tiles.EventBuilders, androidx.wear.tiles.RequestBuilders, androidx.wear.tiles.TileBuilders y androidx.wear.tiles.TileService deben permanecer iguales.

  3. Cambia el nombre de algunos métodos obsoletos de las clases TileService y TileBuilder:

    1. TileBuilders: getTimeline() por getTileTimeline() y setTimeline() por setTileTimeline()
    2. TileService: onResourcesRequest() por onTileResourcesRequest()
    3. RequestBuilders.TileRequest: getDeviceParameters() por getDeviceConfiguration(), setDeviceParameters() por setDeviceConfiguration(), getState() por getCurrentState() y setState() por setCurrentState()