Pulsa para ir al contenido principal

Cómo crear una aplicación de Wear OS para tu aplicación Capacitor

Construya una aplicación para Wear OS para su aplicación Capacitor: agregue un módulo de Wear OS, escriba el lado de reloj en Kotlin y sincronice los datos a través de la Capa de Datos con @capgo/capacitor-reloj.

Créditos del artículo

Martín Donadieu

Escritor

Valeria

Revisor

Jordan

Editor

Cómo construir una aplicación para Wear OS para su aplicación Capacitor

Para crear una aplicación de Wear OS para tu aplicación Capacitor, agrega un módulo nativo de Wear OS a la android/ proyecto, asigna al mismo la misma ID de aplicación y clave de firma que la aplicación del teléfono, y conecta los dos con @capgo/capacitor-watch, which uses Google’s Wear OS Data Layer on Android. The phone keeps its TypeScript code, and the watch runs a small Kotlin app that exchanges JSON with it.

Esta guía muestra cómo configurar el módulo, los caminos exactos de la capa de datos que utiliza el plugin, el Kotlin code en el reloj, el TypeScript code en el teléfono, la prueba con emuladores y la publicación en Google Play.

Cómo funciona la conexión del Capacitor a Wear OS

Wear OS no tiene un Capacitor de tiempo de ejecución, por lo que la aplicación de reloj es nativa. El plugin habla la Capa de Datos API.play-services-wearable) en el teléfono, y la aplicación de tu reloj habla el mismo protocolo.

Mecanismo de la capa de datos Mecanismo de Capa de Datos Path
sendMessage (reloj a teléfono) MessageClient /capgo/message
messageReceived (móvil a reloj) MessageClient /capgo/message
messageReceivedWithReply MessageClient /capgo/message/withreply
replyToMessage MessageClient /capgo/reply/{callbackId}
updateApplicationContext / applicationContextReceived DataClient DataItem /capgo/context
transferUserInfo / userInfoReceived DataClient DataItem /capgo/userinfo/{uuid}

Mensajes transportan JSON UTF-8 como bytes crudos. Los DataItems transportan el mismo JSON como una cadena bajo la clave DataMap payload. El teléfono busca una aplicación de reloj buscando la capacidad capgo_watch.

Conocer estos caminos es la trucada. Una vez que el reloj los lee y escribe, todos los métodos de plugins funcionan.

Lo que necesitas

  • Una Capacitor 8 aplicación con la plataforma Android agregada (guía de actualización si estás detrás)
  • Una reciente Android Studio con las imágenes del sistema Wear OS instaladas
  • Un teléfono con servicios de Google Play. El Capa de datos depende de él, por lo que los teléfonos sin servicios de Play reportan isSupported: false.
  • Un reloj Wear OS o emulador pairado con un teléfono Android. Los relojes Wear OS pairados con un iPhone no pueden alcanzar tu aplicación

Paso 1: Instalar el plugin

bun add @capgo/capacitor-watch
bunx cap sync android

La misma biblioteca maneja Apple Watch en iOS, por lo que una aplicación transversal necesita una sola importación. Consulte el plugin docs para la referencia completa, y nuestro Guía de Apple Watch para el lado iOS.

Step 2: Agregue un módulo de Wear OS al proyecto de Android

Open android/ en Android Studio (bunx cap open androidy usar Archivo > Nuevo > Nuevo Módulo > Wear OS. Letting Android Studio generate the module gives you Gradle files that match your Android Gradle Plugin and Kotlin versions, which is safer than pasting a build file from a blog post. Name the module wear.

El hechicero agrega include ':wear' to android/settings.gradleManténgalo, y deja las líneas gestionadas por Capacitor intactas.

Dos reglas que la Capa de Datos impone

  1. Lo mismo applicationId. En wear/build.gradle, establecer applicationId a exactamente el valor en android/app/build.gradle. El namespace El ID de la aplicación no puede ser igual.
  2. El mismo certificado de firma. Los compilados de depuración en una máquina comparten el keystore de depuración, por lo que esto suele funcionar localmente y luego falla en la versión de liberación. Firma el compilado de liberación de Wear con la misma clave de carga como la aplicación de teléfono.

Si se rompe alguna de estas reglas, las dos aplicaciones nunca se ven entre sí y nada en tu code lo arreglará.

Dependencias y mínimo SDK

Wear OS 3 and later are based on API 30, so minSdk 30 es una plataforma práctica para una nueva aplicación de reloj. Agregue la capa de datos y los ayudantes de coroutines:

dependencies {
    implementation "com.google.android.gms:play-services-wearable:20.0.1"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.11.0"
    // Compose for Wear OS dependencies generated by the wizard stay as they are
}

Declarar la capacidad

Crear wear/src/main/res/values/wear.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@array/android_wear_capabilities">
    <string-array name="android_wear_capabilities" translatable="false">
        <item>capgo_watch</item>
    </string-array>
</resources>

La cadena debe ser capgo_watch, porque ese es lo que el plugin consulta para calcular isWatchAppInstalled.

Marque la aplicación como una aplicación de reloj

In wear/src/main/AndroidManifest.xml, el asistente agrega <uses-feature android:name="android.hardware.type.watch" />. Decida si la aplicación funciona sin el teléfono. Un compañero que solo muestra datos de teléfono debe declarar:

<meta-data
    android:name="com.google.android.wearable.standalone"
    android:value="false" />

Paso 3: Recibir datos en el reloj

Un WearableListenerService Permite que el sistema envíe eventos de Capa de Datos incluso cuando la interfaz de usuario del reloj está cerrada. Regístrelo con filtros para el /capgo paths:

<service
    android:name=".PhoneListenerService"
    android:exported="true">
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
        <data android:scheme="wear" android:host="*" android:pathPrefix="/capgo" />
    </intent-filter>
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
        <data android:scheme="wear" android:host="*" android:pathPrefix="/capgo" />
    </intent-filter>
</service>

A small shared store that the UI can observe:

package com.example.shop.wear

import kotlinx.coroutines.flow.MutableStateFlow
import org.json.JSONObject

object WatchStore {
    val context = MutableStateFlow(JSONObject())
    val lastMessage = MutableStateFlow(JSONObject())
    val lastReply = MutableStateFlow(JSONObject())
}

El servicio analiza cada ruta:

package com.example.shop.wear

import com.google.android.gms.wearable.DataEvent
import com.google.android.gms.wearable.DataEventBuffer
import com.google.android.gms.wearable.DataMapItem
import com.google.android.gms.wearable.MessageEvent
import com.google.android.gms.wearable.WearableListenerService
import org.json.JSONObject

class PhoneListenerService : WearableListenerService() {

    override fun onMessageReceived(event: MessageEvent) {
        val json = JSONObject(String(event.data, Charsets.UTF_8))
        when {
            event.path == "/capgo/message" -> WatchStore.lastMessage.value = json
            event.path.startsWith("/capgo/reply/") -> WatchStore.lastReply.value = json
        }
    }

    override fun onDataChanged(events: DataEventBuffer) {
        for (event in events) {
            if (event.type != DataEvent.TYPE_CHANGED) continue
            val path = event.dataItem.uri.path ?: continue
            val payload = DataMapItem.fromDataItem(event.dataItem)
                .dataMap.getString("payload") ?: "{}"
            val json = JSONObject(payload)

            when {
                path == "/capgo/context" -> WatchStore.context.value = json
                path.startsWith("/capgo/userinfo/") -> handleUserInfo(json)
            }
        }
    }

    private fun handleUserInfo(json: JSONObject) {
        // Persist or act on queued events from the phone
    }
}

Cuando la interfaz de usuario del reloj se abre, también lee el contexto actual directamente para mostrar los datos que llegaron mientras la aplicación estaba cerrada:

suspend fun loadCurrentContext(context: android.content.Context) {
    val items = Wearable.getDataClient(context).dataItems.await()
    try {
        for (item in items) {
            if (item.uri.path == "/capgo/context") {
                val payload = DataMapItem.fromDataItem(item).dataMap.getString("payload") ?: "{}"
                WatchStore.context.value = JSONObject(payload)
            }
        }
    } finally {
        items.release()
    }
}

Paso 4: Enviar datos desde el reloj

El reloj envía mensajes a cada nodo de teléfono conectado. Los mensajes son para interacciones en vivo, los DataItems para el estado.

package com.example.shop.wear

import android.content.Context
import com.google.android.gms.wearable.PutDataMapRequest
import com.google.android.gms.wearable.Wearable
import kotlinx.coroutines.tasks.await
import org.json.JSONObject
import java.util.UUID

object PhoneLink {

    suspend fun send(context: Context, data: JSONObject, expectReply: Boolean = false) {
        val path = if (expectReply) "/capgo/message/withreply" else "/capgo/message"
        val bytes = data.toString().toByteArray(Charsets.UTF_8)
        val nodes = Wearable.getNodeClient(context).connectedNodes.await()
        for (node in nodes) {
            Wearable.getMessageClient(context).sendMessage(node.id, path, bytes).await()
        }
    }

    suspend fun updateContext(context: Context, data: JSONObject) {
        // Include a timestamp: identical DataItems are not redelivered
        data.put("updatedAt", System.currentTimeMillis())
        val request = PutDataMapRequest.create("/capgo/context").apply {
            dataMap.putString("payload", data.toString())
            setUrgent()
        }.asPutDataRequest()
        Wearable.getDataClient(context).putDataItem(request).await()
    }

    suspend fun queueEvent(context: Context, data: JSONObject) {
        val request = PutDataMapRequest.create("/capgo/userinfo/${UUID.randomUUID()}").apply {
            dataMap.putString("payload", data.toString())
            setUrgent()
        }.asPutDataRequest()
        Wearable.getDataClient(context).putDataItem(request).await()
    }
}

Cuando el teléfono recibe un /capgo/userinfo/... Cuando el teléfono recibe un userInfoReceived y elimina el elemento, por lo que la cola no crece infinitamente.

Una pantalla de Compose minimal la une.

@Composable
fun OrdersScreen() {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    val state by WatchStore.context.collectAsState()
    val reply by WatchStore.lastReply.collectAsState()

    LaunchedEffect(Unit) { loadCurrentContext(context) }

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text("${state.optInt("openOrders", 0)} open orders")
        Button(onClick = {
            scope.launch {
                PhoneLink.send(context, JSONObject().put("action", "refresh"), expectReply = true)
            }
        }) { Text("Refresh") }
        if (reply.has("count")) Text("Phone: ${reply.getInt("count")}")
    }
}

Una pantalla de Compose minimal la une: callbackId, so the watch cannot match a reply to a specific request. If you fire several requests at once, include your own request ID in the payload and echo it back in the reply.

Step 5: Habla con el reloj desde tu Capacitor code

El lado de TypeScript es el mismo code que escribirías para Apple Watch:

import { Capacitor } from '@capacitor/core';
import { CapgoWatch } from '@capgo/capacitor-watch';

export async function initWearBridge(loadOpenOrders: () => Promise<number>) {
  if (Capacitor.getPlatform() === 'web') return;

  await CapgoWatch.addListener('messageReceivedWithReply', async ({ message, callbackId }) => {
    if (message.action === 'refresh') {
      const count = await loadOpenOrders();
      await CapgoWatch.replyToMessage({ callbackId, data: { count } });
    }
  });

  await CapgoWatch.addListener('applicationContextReceived', ({ context }) => {
    console.log('watch state', context);
  });

  await CapgoWatch.addListener('userInfoReceived', ({ userInfo }) => {
    console.log('queued event from watch', userInfo);
  });
}

export async function syncToWatch(openOrders: number) {
  const info = await CapgoWatch.getInfo();
  if (!info.isSupported || !info.isWatchAppInstalled) return;

  await CapgoWatch.updateApplicationContext({ context: { openOrders } });

  if (info.isReachable) {
    await CapgoWatch.sendMessage({ data: { toast: 'Orders updated' } });
  }
}

Detalles específicos de Android que debes saber:

  • sendMessage rechaza con “No se encontraron dispositivos Wear OS conectados” cuando no hay un nodo de reloj conectado.
  • isPaired y isReachable ambos significan “al menos un nodo de Wear OS está conectado”. isWatchAppInstalled significa que un nodo anuncia capgo_watch.
  • La alcance no se envía como un evento en Android hoy. Llama getInfo() cuando necesitas un valor fresco, por ejemplo, cuando el pantalla obtiene el foco.
  • Las respuestas pendientes expiran después de cinco minutos en el lado del teléfono.

¿Qué canal usar

necesitas Usar ¿Por qué
Live button press, watch app open sendMessage / /capgo/message Rápido, pero se cae si no hay un nodo conectado
¿Qué debe mostrar el reloj updateApplicationContext / /capgo/context El último valor se mantiene y se sincroniza más tarde
Eventos que no deben perderse transferUserInfo / /capgo/userinfo/{uuid} Cada evento es su propio DataItem

Limitaciones de la capa de datos

  • Los DataItems están limitados a unos 100 KB. Los mensajes también deben ser pequeños. Envíe IDs y recupere contenido desde su servidor backend.
  • Escribir un DataItem con contenido idéntico no desencadena un evento de cambio. Agregue una marca de tiempo si necesita un evento fresco.
  • Un mensaje a la reloj inicia su WearableListenerService, no tu interfaz.
  • Mientras el proceso de la aplicación Capacitor se ejecuta, los oyentes del plugin existen en el teléfono. Los mensajes enviados mientras está muerto no se reproducen en JavaScript. Los DataItems permanecen en la Capa de Datos, por lo que utilice contexto para cualquier cosa que el teléfono deba ver más tarde.
  • Los payloads son objetos JSON. No se pueden parsear arrays de nivel superior ni valores no JSON.

Prueba con emuladores y dispositivos

  1. Crea un emulador de teléfono con una imagen del sistema Google Play y un emulador de Wear OS en Administrador de dispositivos.
  2. Utiliza la asistente de pareja de Wear OS de Android Studio para pairarlos. Instala la aplicación de compañero en el emulador de teléfono.
  3. Ejecuta la wear configuración en el emulador de reloj, luego ejecuta tu aplicación Capacitor en el teléfono con bunx cap run android.
  4. Llama CapgoWatch.getInfo() desde el teléfono. isWatchAppInstalled: true confirma la compatibilidad y la correspondencia del paquete.

Habilite las opciones de desarrollador y la depuración de ADB por Wi-Fi en el reloj, luego conecte con adb connect <watch-ip>:<port>.

Troubleshooting

isSupported: false. Google Play services o el Wear OS API no están instalados en el teléfono. Común en emuladores sin imágenes de Play.

isWatchAppInstalled: false Mientras un reloj está conectado. La capgo_watch La capacidad está ausente o mal escrita, o la aplicación de reloj no está instalada en ese reloj.

Los mensajes nunca llegan al reloj. Diferentes applicationId o diferentes claves de firma. Compare ./gradlew :app:signingReport Compare ./gradlew :wear:signingReport.

Actualizaciones de contexto ya no llegan. Estás escribiendo el mismo payload repetidamente. Agrega un campo que cambie.

Release build works on debug but not on Play. Si utiliza la firma de aplicaciones de Play, tanto los artefactos de teléfono como los de reloj deben estar firmados con la misma clave de firma de aplicaciones en la misma aplicación de Play Console.

R8 elimina su servicio. Mantenga las clases referenciadas solo desde el manifiesto si utiliza reglas de reducción personalizadas.

Publicación en Google Play

La aplicación de Wear OS se envía bajo la misma aplicación de Play Console que la aplicación de teléfono, ya que comparten el nombre de paquete. Agregue la forma factor de Wear OS en la aplicación de Play Console, suba el paquete de Wear OS a un track de lanzamiento de Wear OS y agregue capturas de pantalla de reloj. Las revisiones de Google de las aplicaciones de Wear OS las evalúan contra las directrices de calidad de Wear OS, por lo que revise los diseños de pantalla redonda y los tamaños de objetivo de toque.

Para las actualizaciones, Capgo actualizaciones en vivo pueden enviar correcciones a la capa web de la aplicación de teléfono sin una publicación en la tienda, lo que cubre los errores en su puente de TypeScript code. Los cambios en Kotlin en el reloj pasan por Google Play. Versione sus payloads JSON para que un paquete de teléfono más nuevo se mantenga compatible con una versión más antigua del reloj. Si construye la aplicación de teléfono en la nube, Capgo Construcción gestiona el Capacitor compilación de Android y firma, y puede generar un keystore que coincida con el __CAPGO_KEEP_0__ Generador de keystore de Android.

Resumen

Una compañía para un dispositivo Wear OS de un Capacitor aplicación se reduce a tres piezas: @capgo/capacitor-watch en el teléfono, un módulo de Kotlin que comparte el ID de aplicación y la clave de firma del teléfono, y /capgo/* Rutas de capa de datos entre ellas. Primero obtén la capacidad y la firma correctas, luego el code es corto.

Actualizaciones en vivo para aplicaciones Capacitor

Cuando un error en la capa web está en vivo, envíe la corrección a través de Capgo en lugar de esperar días para la aprobación de la tienda de aplicaciones. Los usuarios obtienen la actualización en segundo plano mientras los cambios nativos siguen en el camino de revisión normal.

Últimas noticias de nuestro Blog

Capgo te brinda las mejores perspectivas que necesitas para crear una aplicación móvil verdaderamente profesional.