Per costruire un'app Wear OS per il tuo Capacitor app, aggiungi un modulo nativo Wear OS al tuo progetto. android/ progetto, assegna allo stesso l'ID dell'applicazione e la chiave di firma del telefono e collega i due 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.
This guide walks through the module setup, the exact Data Layer paths the plugin uses, the Kotlin code on the watch, the TypeScript code on the phone, testing with emulators, and publishing on Google Play.
Come funziona il ponte Capacitor per Wear OS
Wear OS non ha alcun runtime Capacitor, quindi l'app per orologio è nativa. Il plugin parla il Layer dei dati API.play-services-wearable) sul telefono, e l'applicazione del tuo orologio parla lo stesso protocollo.
| Metodo del plugin / evento | Meccanismo di layer di dati | Percorso |
|---|---|---|
sendMessage (telefono a orologio) |
MessageClient |
/capgo/message |
messageReceived (orologio a telefono) |
MessageClient |
/capgo/message |
messageReceivedWithReply |
MessageClient |
/capgo/message/withreply |
replyToMessage |
MessageClient |
/capgo/reply/{callbackId} |
updateApplicationContext / applicationContextReceived |
DataClient DatiItem |
/capgo/context |
transferUserInfo / userInfoReceived |
DataClient DatiItem |
/capgo/userinfo/{uuid} |
I messaggi trasportano JSON UTF-8 come byte raw. I DatiItem trasportano lo stesso JSON come stringa sotto la chiave DataMap payloadIl telefono cerca un'app orologio cercando la capacità capgo_watch.
Sapere questi percorsi è la vera astuzia. Una volta che l'orologio legge e scrive su di essi, ogni metodo del plugin funziona
Cosa ti serve
- Un Capacitor 8 app con la piattaforma Android aggiunta (Guida all'aggiornamento se sei in ritardo).
- Un recente Android Studio con le immagini del sistema Wear OS installate.
- Un telefono con Google Play services. Il Layer dei dati dipende da esso, quindi i telefoni senza servizi Play segnalano
isSupported: false. - Un orologio Wear OS o un emulatore collegato a un telefono Android. Gli orologi Wear OS collegati a un iPhone non possono raggiungere la tua app.
Passo 1: Installa il plugin
bun add @capgo/capacitor-watch
bunx cap sync android
Lo stesso pacchetto gestisce l'Apple Watch su iOS, quindi un'applicazione cross-platform ne richiede uno. Vedi il plugin docs per la documentazione completa, e il nostro guida per l'Apple Watch per il lato iOS.
Passo 2: Aggiungi un modulo Wear OS al progetto Android
Apri android/ in Android Studio (bunx cap open android) e utilizza File > Nuovo > Nuovo modulo > Wear OS. Lasciare che Android Studio generi il modulo ti dà file Gradle che corrispondono alle tue versioni di Android Gradle Plugin e Kotlin, il che è più sicuro che copiare un file di build da un post del blog. Nomi il modulo wear.
La guida aggiunge include ':wear' a android/settings.gradle. Mantienilo e lascia inalterate le righe gestite da Capacitor.
Due regole che il Data Layer impone
- Lo stesso
applicationId. Inwear/build.gradle, impostareapplicationIdper esattamente il valore inandroid/app/build.gradle. Ilnamespacepuò differire, ma l'ID dell'applicazione non può. - Lo stesso certificato di firma. Il build di debug su una macchina condivide lo store di debug, quindi funziona di solito localmente e poi si rompe in rilascio. Sottoscrivi il build di rilascio per orologi con lo stesso chiave di caricamento dell'app del telefono.
Se una delle regole è violata, gli due app non si vedono mai e nulla nel tuo code può risolvere il problema.
Dipendenze e minimo SDK
Wear OS 3 e versioni successive sono basate su API 30, quindi minSdk 30 è un tetto pratico per un'app di orologio nuova. Aggiungi la Data Layer e gli aiuti dei coroutine:
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
}
Declara la capacità
Crea 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 stringa deve essere capgo_watchperché è questo che il plugin richiede per calcolare isWatchAppInstalled.
Segnala l'app come un'app orologio
In wear/src/main/AndroidManifest.xml, il wizard aggiunge <uses-feature android:name="android.hardware.type.watch" />. Decidi se l'app funziona senza il telefono. Un compagno che mostra solo i dati del telefono dovrebbe dichiarare:
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="false" />
Passo 3: Ricevi dati sull'orologio
A WearableListenerService Lascia che il sistema invii eventi del Layer dei dati anche quando l'interfaccia utente del orologio è chiusa. Registralo con filtri per il /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>
Un piccolo archivio condiviso che la UI può osservare:
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())
}
Il servizio elabora ogni percorso:
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
}
}
Quando l'interfaccia utente del orologio si apre, leggi anche il contesto corrente direttamente affinché mostri i dati che sono arrivati mentre l'applicazione era chiusa.
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()
}
}
Passo 4: Invia dati dal orologio
Il orologio invia messaggi a ogni telefono connesso. I messaggi sono per interazioni in tempo reale, i DataItems per lo stato.
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()
}
}
Quando il telefono riceve un /capgo/userinfo/... elemento che emette userInfoReceived e cancella l'elemento, quindi la coda non cresce all'infinito.
Una schermata Compose minimale lo lega tutto:
@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")}")
}
}
Il telefono genera il callbackIdcosì l'orologio non può corrispondere una risposta a una richiesta specifica. Se invii più richieste contemporaneamente, includi il tuo ID di richiesta nel payload e ripetilo indietro nella risposta.
Step 5: Talk to the watch from your Capacitor code
La parte TypeScript è la stessa code che scriveresti per 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' } });
}
}
Specifiche Android da sapere:
sendMessagerifiuta con “Nessun dispositivo Wear OS connesso trovato” quando non è presente alcun nodo di orologio connesso.isPairedandisReachableentrambi significano “almeno un nodo Wear OS è connesso”.isWatchAppInstalledsignifica che un nodo annunciacapgo_watch.- Quando hai bisogno di un valore fresco, ad esempio quando lo schermo riceve l'attenzione.
getInfo()Le risposte pendenti scadono dopo cinque minuti sul lato del telefono. - Risposte in attesa scadono dopo cinque minuti sul lato del telefono.
Sei in grado di
| Usa | Perché | Why |
|---|---|---|
| Velocità, ma scartato se non c'è un nodo connesso | sendMessage / /capgo/message |
Rapido, ma si disconnette se non è presente alcun nodo. |
| Cosa il watch dovrebbe visualizzare | updateApplicationContext / /capgo/context |
Latest value is kept and synced later |
| Eventi che non possono essere persi | transferUserInfo / /capgo/userinfo/{uuid} |
Ogni evento è un proprio DataItem |
Limiti della Data Layer
- I dati sono limitati a circa 100 KB. I messaggi dovrebbero essere piccoli. Invia gli ID e recupera il contenuto dal tuo backend.
- Scrivere un DataItem con contenuto identico non attiva un evento di modifica. Aggiungi un timestamp quando hai bisogno di un evento fresco.
- Un messaggio al watch attiva il tuo
WearableListenerServicenon il tuo UI. - Sul telefono, gli ascoltatori del plugin esistono mentre il processo dell'app Capacitor è in esecuzione. I messaggi inviati mentre è morto non vengono riprodotti in JavaScript. I DataItems rimangono nella Data Layer, quindi utilizza il contesto per qualsiasi cosa il telefono deve vedere in seguito.
- Carichi sono oggetti JSON. Array di livello superiore e valori non JSON non verranno elaborati.
Testare con emulatori e dispositivi
- Crea un emulatore di telefono con un'immagine del sistema Google Play e un emulatore di Wear OS nel Device Manager.
- Usa l'assistente di pairing di Wear OS di Android Studio per accoppiarli. Si installa l'app di accompagnamento sull'emulatore di telefono.
- Esegui il
wearconfigurazione sull'emulatore orologio, quindi esegui il tuo Capacitor app sul telefono conbunx cap run android. - Chiamata
CapgoWatch.getInfo()dal telefono.isWatchAppInstalled: trueconferma la capacità e la corrispondenza del pacchetto.
Per un orologio fisico, abilita le opzioni dello sviluppatore e il debug ADB tramite Wi-Fi sull'orologio, quindi collegati con adb connect <watch-ip>:<port>.
Risolvere i problemi
isSupported: false. Google Play services o il Wear OS API manca sul telefono. Comune sugli emulatori senza immagini Play.
isWatchAppInstalled: false mentre un orologio è collegato. Il capgo_watch la capacità manca o è sbagliata, o l'app per l'orologio non è installata su quel dispositivo.
I messaggi non arrivano mai sull'orologio. Diverso applicationId o chiavi di firma diverse. Confronta ./gradlew :app:signingReport e ./gradlew :wear:signingReport.
Aggiornamenti di contesto non arrivano più. Stai scrivendo lo stesso payload ripetutamente. Aggiungi un campo che cambia.
La versione di rilascio funziona su debug ma non su Play. Se utilizzi l'app di firma Play, sia l'artifact del telefono che quello dell'orologio devono essere firmati con la stessa chiave di firma dell'app di Play Console.
R8 elimina il tuo servizio. Mantieni le classi che si riferiscono solo al manifesto se utilizzi regole di riduzione personalizzate.
Pubblicazione su Google Play
L'app Wear OS viene distribuita sotto lo stesso console di Play dell'app del telefono, poiché condividono il nome del pacchetto. Aggiungi il fattore di forma Wear OS nel console di Play, carica il bundle Wear OS su un tracciato di rilascio Wear OS e aggiungi le schermate del orologio. Le recensioni di Google valutano le app Wear OS in base alle linee guida di qualità Wear OS, quindi controlla le disposizioni a schermo rotondo e le dimensioni dei bersagli di tocco.
Per le aggiornamenti, Capgo aggiornamenti in tempo reale possono inviare riparazioni all'app del telefono del layer web senza una rilascio di negozio, che copre i bug nel tuo ponte di TypeScript code. Le modifiche Kotlin sull'orologio passano attraverso Google Play. Versiona i payload JSON in modo che un bundle di telefono più recente rimanga compatibile con un build di orologio più vecchio. Se costruisci l'app del telefono in cloud, Capgo Costruisci gestisce il Capacitor build Android e la firma, e puoi generare un keystore corrispondente con l' Generatore di keystore Android.
Riassunto
Un companion Wear OS per un'app Capacitor si riduce a tre pezzi: @capgo/capacitor-watch sul telefono, un modulo Kotlin che condivide l'ID di applicazione e la chiave di firma del telefono, e i /capgo/* percorsi del layer dati tra loro. Ottieni la capacità e la firma giusta per primo, poi il code è breve.