Criar seu primeiro bloco no Wear OS

1. Introdução

Três blocos: condicionamento físico, mensagens e agenda.

Os Blocos do Wear OS oferecem fácil acesso às informações e ações de que os usuários precisam para realizar tarefas. Com um simples gesto de deslizar no mostrador do relógio, o usuário pode ver a previsão mais recente ou iniciar um timer.

Um bloco é executado como parte da IU do sistema em vez de ser executado no próprio contêiner do aplicativo. Usamos um Serviço para descrever o layout e o conteúdo do bloco. A IU do sistema vai renderizar o bloco quando necessário.

O que você vai fazer

656045bed9c45083.png

Você vai criar um bloco para um app de mensagens que mostra conversas recentes. Nele, o usuário pode realizar três tarefas comuns:

  • Abrir uma conversa
  • Escrever uma nova mensagem

O que você vai aprender

Neste codelab, você vai aprender a criar seu próprio bloco do Wear OS, incluindo como:

  • Criar um TileService.
  • Testar um bloco em um dispositivo.
  • Visualizar a IU de um bloco no Android Studio.
  • Desenvolver a IU de um bloco.
  • Adicionar imagens
  • Processar interações

Pré-requisitos

2. Etapas da configuração

Nesta etapa, você configurará seu ambiente e fará o download de um projeto inicial.

O que é preciso

Caso não saiba usar o Wear OS, leia este guia rápido (em inglês) antes de começar. Ele inclui instruções para a configuração do emulador do Wear OS e descreve como navegar pelo sistema.

Fazer o download do código

Se você tiver o git instalado, execute o comando abaixo para clonar o código deste repositório (link em inglês).

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

Caso você não tenha o git, clique no botão abaixo para fazer o download de todo o código para este codelab:

Abrir o projeto no Android Studio

Na janela "Welcome to Android Studio", selecione c01826594f360d94.png Open an Existing Project ou File > Open e selecione a pasta [Download Location]

3. Criar um bloco básico

O ponto de entrada de um bloco é o serviço de bloco. Nesta etapa, você vai registrar um serviço de bloco e definir um layout para o bloco.

HelloWorldTileService

Uma classe que implementa o TileService precisa especificar dois métodos:

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

O primeiro método retorna um objeto Resources que mapeia IDs de string para os recursos de imagem que vamos usar no bloco.

A segunda retorna uma descrição de um bloco, incluindo o layout dele. É aqui que definimos o layout de um bloco e como os dados são vinculados a ele.

Abra HelloWorldTileService.kt no módulo start. Todas as mudanças vão ser feitas neste módulo. Há também um módulo finished se você quiser dar uma olhada no resultado deste codelab.

O HelloWorldTileService estende o SuspendingTileService, um wrapper com suporte para corrotinas do Kotlin da biblioteca Horologist Tiles (link em inglês). O Horologist é um grupo de bibliotecas do Google que visa fornecer aos desenvolvedores do Wear OS recursos que normalmente são exigidos por eles, mas que ainda não estão disponíveis no Jetpack.

O SuspendingTileService fornece duas funções de suspensão, que são equivalentes de corrotina das funções do TileService:

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

Para saber mais sobre corrotinas, consulte a documentação sobre Corrotinas do Kotlin no Android.

O HelloWorldTileService ainda não foi concluído. Precisamos registrar o serviço no nosso manifesto e fornecer uma implementação para o tileLayout.

Registrar o serviço do bloco

Depois que o serviço do bloco é registrado no manifesto, ele aparece na lista de blocos disponíveis para o usuário adicionar.

Adicione o <service> ao 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>

O ícone e o rótulo são usados, como um marcador, quando o bloco é carregado pela primeira vez ou se ocorre um erro ao carregar o bloco. Os metadados no fim definem uma imagem de visualização que é mostrada no carrossel quando o usuário está adicionando um bloco.

Definir um layout para o bloco

O HelloWorldTileService tem uma função com o nome tileLayout com uma TODO() como o corpo. Vamos substituir isso por uma implementação em que definimos o layout do bloco:

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

Você criou seu primeiro bloco do Wear OS. Vamos instalar esse bloco e ver a aparência dele.

4. Testar o bloco em um dispositivo

Com o módulo inicial selecionado no menu suspenso para a configuração de execução, você pode instalar o app (o módulo start) no dispositivo ou emulador e instalar manualmente o bloco, como um usuário faria.

No entanto, o Android Studio tem um atalho para fazer isso: toque no ícone Run service (▷) no gutter e selecione "Run ‘HelloWorldTileService'" para instalar e iniciar o bloco em um dispositivo conectado.

ded9f9355abd02f3.png

Selecione "Run ‘HelloWorldTileService'" para criar e executar o bloco em um dispositivo conectado. Ele vai ficar parecido com a captura de tela abaixo.

693c130912097be6.png

O ícone de mão acenando que aparece na parte de cima da tela é fornecido pelo sistema. Para mudá-lo, edite a propriedade android:icon do elemento <service> do bloco no manifesto.

Para sua conveniência, esse processo também vai criar uma configuração de execução "HelloWorldTileService" para uso futuro.

b3335148771abbeb.png

5. Adicionar funções de visualização

Podemos conferir a visualização da interface do bloco no Android Studio. Isso encurta o ciclo de feedback ao desenvolver a interface, aumentando a velocidade de desenvolvimento.

Adicione uma visualização de bloco para o HelloWorldTileService no final do arquivo 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()
    }
}

Use o modo de edição "Split" para conferir o bloco:

tela dividida do Android Studio com o código de visualização à esquerda e uma imagem do bloco à direita.

Observe que a anotação @Composable não é fornecida. Embora os blocos usem a mesma interface de visualização das Funções combináveis, os blocos não usam o Compose e não são combináveis.

6. Criar um bloco de mensagens

cf18db0f604b1999.png

O bloco de mensagens que estamos prestes a criar é mais parecido com um bloco do mundo real. Ao contrário do exemplo HelloWorld, este demonstra os componentes expressivos do Material 3, mostra imagens e processa interações para abrir o app.

MessagingTileService

MessagingTileService estende a classe SuspendingTileService que conferimos anteriormente.

7. Adicionar componentes da interface

A biblioteca ProtoLayout oferece componentes e layouts pré-criados, permitindo que você crie blocos que usam o design expressivo do Material 3 mais recente para Wear OS.

Adicione a dependência do Tiles Material ao arquivo build.gradle:

start/build.gradle

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

Adicione o código do layout à função tileLayout() como o corpo da função materialScope(). Isso cria um layout de duas linhas (com dois botões cada) e um botão de borda.

Encontre a linha "TODO() // Add primaryLayout()" e substitua pelo código abaixo.

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

A função contactButton() no mesmo arquivo cria os botões de contato individuais. Se o contato tiver uma imagem associada, ela vai aparecer no botão. Caso contrário, as iniciais do contato serão usadas.

Você pode notar que, embora o layout geral esteja correto, as imagens estão ausentes:

809bdb9d1213c376.png

Você verá a mesma coisa se implantar o bloco em um dispositivo:

4671bb2eafdcc528.png

Na próxima etapa, vamos corrigir as imagens ausentes.

8. Adicionar imagens

De modo geral, os blocos consistem em dois itens: um layout (que faz referência a recursos por IDs de string) e os próprios recursos (que podem ser imagens).

No momento, nosso código está fornecendo o layout, mas não os recursos. Para corrigir a visualização, precisamos fornecer os recursos da imagem. Para fazer isso, encontre "TODO: Add onTileResourceRequest" e adicione o código abaixo como outro argumento nomeado para 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()
}

As imagens vão aparecer na visualização:

e77d746268f293f2.png

No entanto, se o bloco for implantado em um dispositivo, as imagens vão estar ausentes. Para corrigir isso, substitua a função resourcesRequest() no Service.kt pelo seguinte:

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

Agora as imagens também são mostradas quando o bloco é implantado em um dispositivo:

cf18db0f604b1999.png

Na próxima etapa, vamos processar os cliques em cada um dos elementos.

9. Processar interações

Uma das coisas mais úteis que podemos fazer com um bloco é fornecer atalhos para as jornadas ideais do usuário. Isso é diferente do Acesso rápido aos apps, que apenas abre o app. Aqui, temos espaço para fornecer atalhos contextuais a uma tela específica do app.

Até agora, usamos uma ação fictícia fornecida pelo clickable() sem argumentos para o ícone e cada um dos botões. Isso é bom para visualizações, que não são interativas, mas vamos conferir como adicionar ações aos elementos.

LaunchAction

LaunchAction pode ser usada para iniciar uma atividade. Vamos modificar o Layout para que o toque no botão "New" inicie a jornada de nova conversa para o usuário.

Encontre a linha "TODO: Launch new conversation activity" e substitua 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
                    )
            ),
        ),
)

Implante o bloco novamente. Agora, em vez de não fazer nada, tocar em "New" vai iniciar MainActivity e a jornada de nova conversa do usuário:

a08c28b4a142fb8f.png

Da mesma forma, modifique o Layout para que tocar em um botão de contato inicie uma conversa com um usuário específico.

Encontre a linha "Launch open conversation activity" e substitua o 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
                    ),
            ),
        ),
)

Implante o bloco novamente. Agora, em vez de não fazer nada, tocar em um contato vai iniciar uma conversa com ele:

b684a1ced0b226f9.png

10. Parabéns

Parabéns! Você aprendeu a criar um bloco para Wear OS.

Qual é a próxima etapa?

Para mais informações, confira as Implementações de blocos dourados no GitHub (em inglês), o Guia de blocos do Wear OS e as diretrizes de design.