Per creare un'app Wear OS per la tua app Capacitor, aggiungi un modulo nativo Wear OS al android/ progetto, usa lo stesso application ID e la stessa signing key dell'app per smartphone e connettile tramite @capgo/capacitor-watch, che utilizza il Google Wear OS Data Layer su Android. Il telefono mantiene il proprio code in TypeScript, mentre l'orologio esegue una piccola app Kotlin che scambia JSON con esso.
Questa guida illustra la configurazione del modulo, i percorsi esatti del Data Layer usati dal plugin, il code Kotlin sull'orologio, il code TypeScript sul telefono, i test con emulatori e la pubblicazione su Google Play.
Come funziona il bridge Capacitor verso Wear OS
Wear OS non ha un runtime Capacitor, quindi l'app per l'orologio è nativa. Il plugin comunica tramite il API del Data Layer (play-services-wearable) sul telefono, e la tua app per l'orologio utilizza lo stesso protocollo.
| Metodo/evento del plugin | Meccanismo Data Layer | Percorso |
|---|---|---|
sendMessage (dal telefono all'orologio) |
MessageClient |
/capgo/message |
messageReceived (dall'orologio al telefono) |
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} |
I messaggi trasportano JSON UTF-8 come byte grezzi. I DataItems trasportano lo stesso JSON come stringa sotto la chiave DataMap payload. Il telefono cerca l'app per smartwatch verificando la capability capgo_watch.
Conoscere questi percorsi è il segreto. Una volta che l'orologio legge e scrive, ogni metodo del plugin funziona.
Cosa ti serve
- Un'app Capacitor 8 con la piattaforma Android aggiunta (guida all'upgrade se sei rimasto indietro).
- Una versione recente di Android Studio con le immagini di sistema Wear OS installate.
- Un telefono con Google Play services. Il Data Layer dipende da essi, quindi i telefoni senza Play services segnalano
isSupported: false. - Un orologio Wear OS o un emulatore accoppiato a un telefono Android. Gli smartwatch Wear OS accoppiati a un iPhone non possono raggiungere la tua app.
Passaggio 1: Installa il plugin
bun add @capgo/capacitor-watch
bunx cap sync android
Lo stesso pacchetto gestisce Apple Watch su iOS, quindi un'app cross-platform richiede un solo import. Vedi la docs del plugin per il riferimento completo, e la nostra guida per Apple Watch per la parte iOS.
Passaggio 2: Aggiungi un modulo Wear OS al progetto Android
Apri android/ in Android Studio (bunx cap open android) e usa File > Nuovo > Nuovo modulo > Wear OS. Lasciare che Android Studio generi il modulo ti fornisce i file Gradle compatibili con le tue versioni di Android Gradle Plugin e Kotlin, il che è più sicuro che incollare un file di build da un blog post. Dai un nome al modulo wear.
La procedura guidata aggiunge include ':wear' a android/settings.gradle. Mantienilo e non toccare le righe gestite da Capacitor.
Le due regole imposte dal Data Layer
- Stesso
applicationId. Inwear/build.gradle, impostaapplicationIdesattamente al valore diandroid/app/build.gradle. Il/Lanamespacepuò differire, l'application ID no. - Stesso certificato di firma. Le build di debug su una macchina condividono il debug keystore, quindi spesso funziona in locale ma si rompe in release. Firma la release di wear con la stessa upload key dell'app per telefono.
Se una delle due regole viene violata, le due app non si vedranno mai e nulla in code potrà risolvere il problema.
Dipendenze e SDK minimo
Wear OS 3 e versioni successive si basano su API 30, quindi minSdk 30 rappresentano una base pratica per una nuova app per smartwatch. Aggiungi il Data Layer e gli helper per le 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
}
Dichiara la capability
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_watch, perché è ciò che il plugin interroga per calcolare isWatchAppInstalled.
Segna l'app come app per smartwatch
In wear/src/main/AndroidManifest.xml, il wizard aggiunge <uses-feature android:name="android.hardware.type.watch" />. Decidi se l'app funzioni senza il telefono. Un'app companion che mostra solo i dati del telefono deve dichiarare:
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="false" />
Passaggio 3: Ricevi i dati sullo smartwatch
Un WearableListenerService permette al sistema di inviare eventi Data Layer anche quando l'interfaccia dell'orologio è chiusa. Registralo con i filtri per i /capgo percorsi:
<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 store condiviso che l'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 analizza 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 dell'orologio si apre, leggi direttamente il contesto attuale per mostrare i dati arrivati mentre l'app 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()
}
}
Passaggio 4: Invia i dati dall'orologio
L'orologio invia a ogni nodo telefono connesso. I messaggi servono per interazioni live, 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/... item, emette userInfoReceived e cancella l'item, così la coda non cresce all'infinito.
Una schermata Compose minimale mette tutto insieme:
@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 callbackId, quindi l'orologio non può associare una risposta a una richiesta specifica. Se invii più richieste contemporaneamente, includi un tuo ID richiesta nel payload e restituiscilo nella risposta.
Passaggio 5: Comunica con l'orologio tramite 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 conoscere:
sendMessagerestituisce “No connected Wear OS devices found” se nessun nodo orologio è connesso.isPairedeisReachableentrambi significano “almeno un nodo Wear OS è connesso”.isWatchAppInstalledsignifica che un nodo sta facendo advertisingcapgo_watch.- Oggi la Reachability non viene inviata come evento su Android. Chiama
getInfo()quando serve un valore aggiornato, ad esempio quando lo schermo riceve il focus. - Le risposte in sospeso scadono dopo cinque minuti sul lato telefono.
Quale canale usare
| Necessità | Usa | Perché |
|---|---|---|
| Pressione tasto Live, apertura app | sendMessage / /capgo/message |
Veloce, ma persa se non c'è un nodo |
| Cosa deve mostrare l'orologio | updateApplicationContext / /capgo/context |
L'ultimo valore viene salvato e sincronizzato |
| Eventi che non devono andare persi | transferUserInfo / /capgo/userinfo/{uuid} |
Ogni evento è un DataItem dedicato |
Limiti del Data Layer
- I DataItem sono limitati a circa 100 KB. Anche i messaggi devono essere piccoli. Invia gli ID e recupera i contenuti dal backend.
- Scrivere un DataItem con contenuto identico non genera un evento di cambio. Aggiungi un timestamp se serve un nuovo evento.
- Un messaggio per lo smartwatch avvia il tuo
WearableListenerService, non la tua UI. - Sul telefono, i listener del plugin sono attivi finché il processo dell'app Capacitor è in esecuzione. I messaggi inviati quando il processo è terminato non vengono riprodotti in JavaScript. I DataItem rimangono nel Data Layer, quindi usa il context per tutto ciò che il telefono deve vedere in seguito.
- I payload sono oggetti JSON. Gli array di primo livello e i valori non JSON non verranno analizzati.
Test con emulatori e dispositivi
- Crea un emulatore telefonico con un'immagine di sistema Google Play e un emulatore Wear OS in Device Manager.
- Usa l'assistente di pairing Wear OS di Android Studio per accoppiarli. Installerà l'app companion sull'emulatore del telefono.
- Esegui il
wearconfigurazione sull'emulatore dello smartwatch, quindi avvia la tua app Capacitor sul telefono conbunx cap run android. - Chiama
CapgoWatch.getInfo()dal telefono.isWatchAppInstalled: trueconferma la corrispondenza di capability e package.
Per uno smartwatch fisico, abilita le opzioni sviluppatore e il debug ADB via Wi-Fi sull'orologio, poi connettiti con adb connect <watch-ip>:<port>.
Risoluzione problemi
isSupported: false. Google Play services o il API di Wear OS manca sul telefono. Comune sugli emulatori senza immagini Play.
isWatchAppInstalled: false mentre l'orologio è connesso. L'oggetto capgo_watch la capability manca o è scritta male, oppure l'app wear non è installata su quell'orologio.
I messaggi non arrivano mai sull'orologio. Diversi applicationId o chiavi di firma diverse. Confronta ./gradlew :app:signingReport e ./gradlew :wear:signingReport.
Gli aggiornamenti del contesto smettono di arrivare. Stai scrivendo lo stesso payload ripetutamente. Aggiungi un campo variabile.
La release build funziona in debug ma non su Play. Se usi Play App Signing, sia gli artifact del telefono che quelli dello smartwatch devono essere firmati con la stessa app signing key nella stessa app della Play Console.
R8 rimuove il tuo servizio. Mantieni le classi referenziate solo dal manifest se utilizzi regole di shrinking personalizzate.
Pubblicazione su Google Play
L'app Wear OS viene pubblicata sotto la stessa app della Play Console dell'app per telefono, poiché condividono il package name. Aggiungi il form factor Wear OS nella Play Console, carica il bundle per Wear OS in un track di release Wear OS e aggiungi gli screenshot per smartwatch. Google revisiona le app Wear OS in base alle sue linee guida sulla qualità, quindi controlla i layout per schermi circolari e le dimensioni dei target touch.
Per gli aggiornamenti, live updates Capgo possono inviare fix al web layer dell'app per telefono senza una release sullo store, coprendo i bug nel tuo bridge TypeScript code. Le modifiche Kotlin sullo smartwatch passano tramite Google Play. Versiona i tuoi payload JSON in modo che un bundle per telefono più recente rimanga compatibile con una build dello smartwatch più vecchia. Se buildi l'app per telefono nel cloud, Build con Capgo gestisce la build e la firma Android Capacitor, e puoi generare un keystore corrispondente con l' Generatore di keystore Android.
In sintesi
Un'app companion per Wear OS per un'app Capacitor si basa su tre elementi: @capgo/capacitor-watch sul telefono, un modulo Kotlin che condivide l'ID applicazione e la chiave di firma del telefono, e i /capgo/* percorsi Data Layer intermedi. Configura correttamente permessi e firma, e l'integrazione di code sarà rapidissima.