Crea tu primera tarjeta en Wear OS

1. Introducción

Tres tarjetas: fitness, mensajes y calendario

Las tarjetas de Wear OS brindan un acceso sencillo a la información y a las acciones que los usuarios necesitan para realizar tareas. Con solo deslizar el dedo sobre la cara de reloj, el usuario puede consultar el pronóstico actualizado o iniciar el cronómetro.

Una tarjeta se ejecuta como parte de la IU del sistema en lugar de ejecutarse en el propio contenedor de la aplicación. Usamos un Servicio para describir el diseño y el contenido de la tarjeta. Luego, la IU del sistema renderizará la tarjeta cuando sea necesario.

Actividades

656045bed9c45083.png

Compilarás la tarjeta de una app de mensajería que muestra conversaciones recientes. Desde esa pantalla, el usuario puede pasar a tres tareas comunes:

  • Abrir una conversación
  • Redactar un mensaje nuevo

Qué aprenderás

En este codelab, aprenderás a escribir tu propia tarjeta de Wear OS, incluido lo siguiente:

  • Cómo crear un TileService
  • Cómo probar una tarjeta en un dispositivo
  • Cómo obtener una vista previa de la IU de una tarjeta en Android Studio
  • Cómo desarrollar la IU para una tarjeta
  • Cómo agregar imágenes
  • Cómo controlar interacciones

Requisitos previos

  • Conocimientos básicos sobre Kotlin

2. Prepárate

En este paso, configurarás tu entorno y descargarás un proyecto inicial.

Requisitos

Si no estás familiarizado con el uso de Wear OS, lee esta guía rápida antes de comenzar. Se incluyen instrucciones para configurar un emulador de Wear OS y se describe cómo navegar por el sistema.

Cómo descargar el código

Si ya instalaste git, solo ejecuta el siguiente comando para clonar el código de este repositorio.

git clone https://github.com/android/codelab-wear-tiles.git
cd codelab-wear-tiles

Si no tienes git, puedes hacer clic en el siguiente botón para descargar todo el código de este codelab:

Abre el proyecto en Android Studio

En la ventana "Welcome to Android Studio", selecciona c01826594f360d94.png Open an Existing Project o File > Open y selecciona la carpeta [Ubicación de descarga].

3. Cómo crear una tarjeta básica

El punto de entrada para una tarjeta es el servicio de mosaicos para mapas. En este paso, registrarás un servicio de este tipo y definirás un diseño para la tarjeta.

HelloWorldTileService

Una clase que implementa TileService debe especificar dos métodos:

  • onTileResourcesRequest(requestParams: ResourcesRequest): ListenableFuture<Resources>
  • onTileRequest(requestParams: TileRequest): ListenableFuture<Tile>

El primer método devuelve un objeto Resources que asigna IDs de cadenas a los recursos de imagen que usaremos en la tarjeta.

El segundo devuelve la descripción de una tarjeta, incluido su diseño. Aquí es donde definimos el diseño de la tarjeta y cómo se vinculan los datos a ella.

Abre HelloWorldTileService.kt desde el módulo start. Todos los cambios que hagas estarán en este módulo. También hay un módulo finished si quieres ver el resultado de este codelab.

HelloWorldTileService extiende SuspendingTileService, un wrapper compatible con corrutinas de Kotlin de la biblioteca de tarjetas de Horologist. Horologist es un grupo de bibliotecas de Google que tiene como objetivo aportar a los desarrolladores de Wear OS un complemento con funciones que comúnmente requieren, pero que aún no están disponibles en Jetpack.

SuspendingTileService proporciona dos funciones de suspensión, que son versiones de corrutinas equivalentes de las funciones de TileService:

  • suspend resourcesRequest(requestParams: ResourcesRequest): Resources
  • suspend tileRequest(requestParams: TileRequest): Tile

Si deseas obtener más información sobre las corrutinas, consulta la documentación sobre corrutinas de Kotlin en Android.

HelloWorldTileService aún no está completo. Debemos registrar el servicio en nuestro manifiesto y también proporcionar una implementación para tileLayout.

Cómo registrar el servicio de tarjetas

Una vez que se registre el servicio de tarjetas en el manifiesto, se mostrará en la lista de tarjetas disponibles que puede agregar el usuario.

Agrega el <service> dentro del elemento <application>:

start/src/main/AndroidManifest.xml

<service
    android:name="com.example.wear.tiles.hello.HelloWorldTileService"
    android:icon="@drawable/ic_waving_hand_24"
    android:label="@string/hello_tile_label"
    android:description="@string/hello_tile_description"
    android:exported="true"
    android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
    
    <intent-filter>
        <action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
    </intent-filter>

    <!-- The tile preview shown when configuring tiles on your phone -->
    <meta-data
        android:name="androidx.wear.tiles.PREVIEW"
        android:resource="@drawable/tile_hello" />
</service>

El ícono y la etiqueta se usan (como marcador de posición) cuando la tarjeta se carga por primera vez o si hay un error al momento de cargarla. Los metadatos al final definen una imagen de vista previa que se muestra en el carrusel cuando el usuario agrega una tarjeta.

Cómo definir un diseño para la tarjeta

HelloWorldTileService tiene una función llamada tileLayout con un TODO() como cuerpo. Ahora vamos a reemplazar eso con una implementación en la que definimos el diseño de nuestra tarjeta:

start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt

fun tileLayout(
    context: Context,
    deviceConfiguration: DeviceParametersBuilders.DeviceParameters,
    message: String,
) =
    materialScope(
        context = context,
        deviceConfiguration = deviceConfiguration,
        allowDynamicTheme = false,
    ) {
        primaryLayout(mainSlot = { text(message.layoutString) })
    }

Ya creaste tu primera tarjeta de Wear OS. Instalemos esta tarjeta y veamos cómo se ve.

4. Cómo probar tu tarjeta en un dispositivo

Con el módulo de partida seleccionado en el menú desplegable de la configuración de ejecución, puedes instalar la app (el módulo start) en tu dispositivo o emulador, y, luego, instalar manualmente la tarjeta, como lo haría un usuario.

Sin embargo, Android Studio tiene un atajo para hacerlo: si presionas el ícono "Run service" (▷) en el margen y, luego, seleccionas "Run 'HelloWorldTileService'", se instalará y ejecutará la tarjeta en un dispositivo conectado.

ded9f9355abd02f3.png

Selecciona "Run 'HelloWorldTileService'" para compilar y ejecutar la tarjeta en un dispositivo conectado. Debería verse como en la siguiente captura de pantalla.

693c130912097be6.png

El sistema proporciona el ícono de "mano saludando" que aparece en la parte superior de la pantalla. Para cambiarlo, modifica la propiedad android:icon del elemento <service> de la tarjeta en el manifiesto.

Para mayor comodidad, este proceso también creará una "configuración de ejecución" de "HelloWorldTileService" para uso futuro.

b3335148771abbeb.png

5. Cómo agregar funciones de vista previa

Podemos obtener una vista previa de la IU de tarjetas en Android Studio. Eso reduce el ciclo de retroalimentación cuando se desarrolla la IU, lo que aumenta la velocidad de desarrollo.

Agrega una vista previa de tarjeta para el elemento HelloWorldTileService al final del archivo HelloWorldTileService.kt.

start/src/main/java/com/example/wear/tiles/hello/HelloWorldTileService.kt

@Preview(device = WearDevices.SMALL_ROUND, name = "Small Round")
@Preview(device = WearDevices.LARGE_ROUND, name = "Large Round")
internal fun helloLayoutPreview(context: Context): TilePreviewData {
    return TilePreviewData {
        TilePreviewHelper.singleTimelineEntryTileBuilder(
            helloLayout(context, it.deviceConfiguration, "Hello, preview tile!")
        )
            .build()
    }
}

Usa el modo de editor "Pantalla dividida" para obtener una vista previa de la tarjeta:

Vista de pantalla dividida de Android Studio con el código de la vista previa a la izquierda y una imagen de la tarjeta a la derecha.

Ten en cuenta que la anotación @Composable no se usa. Aunque las tarjetas usan la misma IU de vista previa que las Funciones de componibilidad, las tarjetas no usan Compose y no son componibles.

6. Cómo crear una tarjeta de mensajes

cf18db0f604b1999.png

La tarjeta de mensajería que vamos a crear se parece más a una tarjeta del mundo real. A diferencia del ejemplo de HelloWorld, este muestra los componentes expresivos de Material 3, muestra imágenes y controla las interacciones para abrir la app.

MessagingTileService

MessagingTileService extiende la clase SuspendingTileService que vimos antes.

7. Cómo agregar componentes de la IU

La biblioteca de ProtoLayout proporciona componentes y diseños precompilados, lo que te permite crear tarjetas que contengan lo último en diseño expresivo de Material 3 para Wear OS.

Agrega la dependencia de Tiles Material a tu archivo build.gradle:

start/build.gradle

implementation "androidx.wear.protolayout:protolayout-material3:$protoLayoutVersion"

Agrega el código de diseño a la función tileLayout(), como el cuerpo de la función materialScope(). Esto crea un diseño de dos filas (con dos botones cada una) y un botón de borde.

Busca la línea "TODO() // Add primaryLayout()" y reemplázala por el siguiente código.

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

primaryLayout(
    mainSlot = {
        // This layout code assumes "contacts" contains at least 4 elements, for sample code
        // that can handle an arbitrary number of contacts, and also shows different numbers
        // of contacts based on the physical screen size, see
        // <https://github.com/android/wear-os-samples/tree/main/WearTilesKotlin>.
        Column.Builder()
            .apply {
                setWidth(expand())
                setHeight(expand())
                addContent(
                    buttonGroup {
                        buttonGroupItem { contactButton(contacts[0]) }
                        buttonGroupItem { contactButton(contacts[1]) }
                    }
                )
                addContent(DEFAULT_SPACER_BETWEEN_BUTTON_GROUPS)
                addContent(
                    buttonGroup {
                        buttonGroupItem { contactButton(contacts[2]) }
                        buttonGroupItem { contactButton(contacts[3]) }
                    }
                )
            }
            .build()
    },
    bottomSlot = {
        textEdgeButton(
            onClick = clickable(), // TODO: Launch new conversation activity
            labelContent = { text("New".layoutString) },
        )
    },
)

La función contactButton() en el mismo archivo crea los botones de contacto individuales. Si el contacto tiene una imagen asociada, esta aparecerá en el botón. De lo contrario, se usan las iniciales del contacto.

En este punto, es posible que notes que, aunque el diseño general es correcto, faltan las imágenes:

809bdb9d1213c376.png

Verás lo mismo si implementas la tarjeta en un dispositivo:

4671bb2eafdcc528.png

En el siguiente paso, corregiremos las imágenes faltantes.

8. Cómo agregar imágenes

A un alto nivel, las tarjetas constan de dos elementos: un diseño (que hace referencia a recursos por IDs de cadenas) y los recursos en sí (que pueden ser imágenes).

Por el momento, nuestro código proporciona el diseño, pero no los recursos en sí. Para corregir la vista previa, debemos proporcionar los "recursos" de la imagen. Para ello, busca "TODO: Add onTileResourceRequest" y agrega el siguiente código como argumento nombrado adicional a TilePreviewData():

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

// Additional named argument to TilePreviewData
onTileResourceRequest = { resourcesRequest ->
    Resources.Builder()
        .setVersion(resourcesRequest.version)
        .apply {
            contacts.forEach {
                if (it.avatarSource is AvatarSource.Resource) {
                    addIdToImageMapping(
                        it.imageResourceId(),
                        it.avatarSource.resourceId
                    )
                }
            }
        }
        .build()
}

Las imágenes ahora deberían aparecer en la vista previa:

e77d746268f293f2.png

Sin embargo, si la tarjeta se implementa en un dispositivo, faltarán las imágenes. Para solucionar este problema, reemplaza la función resourcesRequest() en Service.kt por lo siguiente:

start/src/main/java/com/example/wear/tiles/messaging/tile/Service.kt

override suspend fun resourcesRequest(
    requestParams: ResourcesRequest
): Resources {
    // resourceIds is a list of the ids we need to provide images for. If we're passed an empty
    // list, set resourceIds to all resources.
    val resourceIds =
        requestParams.resourceIds.ifEmpty {
            contacts.map { it.imageResourceId() }
        }

    // resourceMap maps (tile) resource ids to (Android) resource ids.
    val resourceMap =
        contacts
            .mapNotNull {
                when (it.avatarSource) {
                    is AvatarSource.Resource ->
                        it.imageResourceId() to
                            it.avatarSource.resourceId
                    else -> null
                }
            }
            .toMap()
            .filterKeys {
                it in resourceIds
            } // filter to only the resources we need

    // Add images in the resourceMap to the Resources object, and return the result.
    return Resources.Builder()
        .setVersion(requestParams.version)
        .apply {
            resourceMap.forEach { (id, imageResource) ->
                addIdToImageMapping(id, imageResource)
            }
        }
        .build()
}

Ahora, las imágenes también se muestran cuando la tarjeta se implementa en un dispositivo:

cf18db0f604b1999.png

En el siguiente paso, controlaremos los clics en cada uno de los elementos.

9. Cómo controlar interacciones

Una de las cosas más útiles que podemos hacer con una tarjeta es proporcionar accesos directos a los recorridos críticos del usuario. Esto es diferente del selector de aplicaciones, que solo abre la app. Aquí tenemos espacio para brindar accesos directos contextuales a una pantalla específica de tu app.

Hasta ahora, usamos una acción simulada proporcionada por el clickable() sin argumentos para el chip y cada uno de los botones. Esto está bien para las vistas previas, que no son interactivas, pero veamos cómo agregar acciones para los elementos.

LaunchAction

Se puede usar LaunchAction para iniciar una actividad. Modifiquemos Layout para que, cuando se presione el botón "Nuevo", se inicie el recorrido del usuario de "nueva conversación".

Busca la línea "TODO: Launch new conversation activity" y reemplaza clickable() por:

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

clickable(
    id = "new_button",
    action =
        launchAction(
            ComponentName(
                "com.example.wear.tiles",
                "com.example.wear.tiles.messaging.MainActivity",
            ),
            mapOf(
                MainActivity.EXTRA_JOURNEY to
                    ActionBuilders.stringExtra(
                        MainActivity.EXTRA_JOURNEY_NEW
                    )
            ),
        ),
)

Vuelve a implementar la tarjeta. Ahora, en lugar de no hacer nada, si presionas "Nuevo", se iniciará MainActivity y comenzará el recorrido del usuario de "nueva conversación":

a08c28b4a142fb8f.png

Del mismo modo, modifica Layout para que presionar un botón de contacto inicie una conversación con un usuario específico.

Busca la línea "Launch open conversation activity" y reemplaza clickable() por:

start/src/main/java/com/example/wear/tiles/messaging/tile/Layout.kt

clickable(
    id = contact.id.toString(),
    action =
        launchAction(
            ComponentName(
                "com.example.wear.tiles",
                "com.example.wear.tiles.messaging.MainActivity",
            ),
            mapOf(
                MainActivity.EXTRA_JOURNEY to
                    ActionBuilders.stringExtra(
                        MainActivity
                            .EXTRA_JOURNEY_CONVERSATION
                    ),
                MainActivity.EXTRA_CONVERSATION_CONTACT to
                    ActionBuilders.stringExtra(
                        contact.name
                    ),
            ),
        ),
)

Vuelve a implementar la tarjeta. Ahora, en lugar de no hacer nada, si presionas un contacto, se iniciará una conversación con él:

b684a1ced0b226f9.png

10. Felicitaciones

¡Felicitaciones! Ya aprendiste a crear una tarjeta para Wear OS.

¿Qué sigue?

Para obtener más información, consulta las implementaciones de tarjetas doradas en GitHub, la guía de tarjetas de Wear OS y los lineamientos de diseño.