Um eine Wear OS-App für deine Capacitor-App zu erstellen, füge ein natives Wear OS-Modul hinzu android/ Projekt hinzu, gib ihm dieselbe Application ID und denselben Signing Key wie der Smartphone-App und verbinde beide mit @capgo/capacitor-watch, das Googles Wear-OS Data Layer unter Android nutzt. Das Telefon behält TypeScript-code, und auf der Uhr läuft eine kleine Kotlin-App, die JSON mit dem Telefon austauscht.
Dieser Leitfaden behandelt die Einrichtung des Moduls, die genauen Data-Layer-Pfade, die das Plugin nutzt, die Kotlin-code auf der Uhr, TypeScript-code auf dem Telefon, das Testen mit Emulatoren und das Veröffentlichen bei Google Play.
So funktioniert die Bridge von Capacitor zu Wear OS
Wear OS verfügt über keine Capacitor-Runtime, daher ist die Watch-App nativ. Das Plugin spricht die Data Layer APIplay-services-wearable) auf dem Telefon, und deine Watch-App spricht dasselbe Protokoll.
| Plugin-Methode / Event | Data-Layer-Mechanismus | Pfad |
|---|---|---|
sendMessage (vom Telefon zur Uhr) |
MessageClient |
/capgo/message |
messageReceived (von der Uhr zum Telefon) |
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} |
Messages enthalten UTF-8-JSON als rohe Bytes. DataItems enthalten dasselbe JSON als String unter dem DataMap-Key. payload. Das Telefon prüft auf eine Watch-App, indem es nach der Capability sucht capgo_watch.
Diese Pfade zu kennen, ist der ganze Trick. Sobald die Uhr darauf liest und schreibt, funktionieren alle Plugin-Methoden.
Voraussetzungen
- Eine Capacitor 8-App mit der hinzugefügten Android-Plattform (Upgrade-Guide falls du zurückliegst).
- Ein aktueller Android Studio mit installierten Wear OS-Systembildern.
- Ein Smartphone mit Google Play-Diensten. Die Data Layer hängt davon ab, daher melden Geräte ohne Play-Dienste
isSupported: false. - Eine Wear OS-Uhr oder ein Emulator, gekoppelt mit einem Android-Smartphone. Wear OS-Uhren, die mit einem iPhone gekoppelt sind, können nicht auf deine App zugreifen.
Schritt 1: Plugin installieren
bun add @capgo/capacitor-watch
bunx cap sync android
Dasselbe Paket deckt Apple Watch unter iOS ab, daher braucht eine plattformübergreifende App nur einen Import. Siehe die Plugin-Docs für die vollständige Referenz und unsere Apple Watch-Anleitung für die iOS-Seite.
Schritt 2: Ein Wear OS-Modul zum Android-Projekt hinzufügen
Öffne android/ in Android Studio (bunx cap open android) und nutze Datei > Neu > Neues Modul > Wear OS. Wenn Android Studio das Modul generiert, erhältst du Gradle-Dateien, die zu deinen Android Gradle Plugin- und Kotlin-Versionen passen – sicherer als eine Build-Datei aus einem Blog-Post zu kopieren. Benenne das Modul wear.
Der Assistent fügt include ':wear' zu android/settings.gradleBehalte es bei und lass die von Capacitor verwalteten Zeilen in Ruhe.
Zwei Regeln, die die Data Layer durchsetzt
- Dasselbe
applicationId. Unterwear/build.gradle, setzenapplicationIdauf exakt den Wert inandroid/app/build.gradle. Dienamespacekönnen abweichen, die Application ID aber nicht. - Gleiches Signaturzertifikat. Debug-Builds auf einem Rechner teilen sich den Debug-Keystore, daher funktioniert das lokal meist, bricht aber im Release. Signiere den Wear-Release-Build mit demselben Upload-Key wie die Phone-App.
Wenn eine der beiden Regeln verletzt wird, sehen sich die beiden Apps nie und nichts in deinem code wird das beheben.
Abhängigkeiten und Mindest-SDK
Wear OS 3 und neuer basieren auf API 30, also minSdk 30 ist ein praktischer Mindestwert für eine neue Watch-App. Füge die Data Layer und die Coroutine-Helfer hinzu:
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
}
Capability deklarieren
Erstellen 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>
Die Zeichenkette muss capgo_watchsein, da das Plugin genau diese abfragt, um isWatchAppInstalled.
App als Watch-App markieren
Unter wear/src/main/AndroidManifest.xml, fügt der Assistent <uses-feature android:name="android.hardware.type.watch" />hinzu. Entscheide, ob die App ohne das Telefon funktioniert. Eine Companion-App, die nur Telefondaten anzeigt, sollte deklarieren:
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="false" />
Schritt 3: Daten auf der Uhr empfangen
Ein WearableListenerService ermöglicht es dem System, Data-Layer-Events zu liefern, auch wenn die Watch-UI geschlossen ist. Registriere es mit Filtern für die /capgo Pfade:
<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>
Ein kleiner Shared Store, den die UI beobachten kann:
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())
}
Der Service parst jeden Pfad:
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
}
}
Wenn die Watch-UI geöffnet wird, lies auch den aktuellen Kontext direkt aus, damit sie Daten anzeigt, die eingegangen sind, während die App geschlossen war:
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()
}
}
Schritt 4: Daten von der Watch senden
Die Watch sendet an jeden verbundenen Phone-Node. Messages sind für Live-Interaktionen, DataItems für den State.
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()
}
}
Wenn das Phone ein /capgo/userinfo/... Item empfängt, emittiert es userInfoReceived und löscht das Item, damit die Queue nicht unendlich wächst.
Ein minimaler Compose-Screen bringt alles zusammen:
@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")}")
}
}
Das Phone generiert das callbackId, damit die Uhr eine Antwort keinem bestimmten Request zuordnen kann. Wenn du mehrere Requests gleichzeitig abschickst, pack deine eigene Request-ID in den Payload und schick sie in der Antwort zurück.
Schritt 5: Mit der Uhr aus deiner Capacitor code sprechen
Die TypeScript-Seite ist derselbe code, den du für die Apple Watch schreiben würdest:
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' } });
}
}
Android-spezifische Details, die du kennen solltest:
sendMessagewird abgelehnt mit „No connected Wear OS devices found“, wenn kein Wear-OS-Knoten verbunden ist.isPairedundisReachablebedeuten beide, dass mindestens ein Wear-OS-Knoten verbunden ist.isWatchAppInstalledbedeutet, dass ein Knoten sich anmeldetcapgo_watch.- Auf Android wird die Erreichbarkeit derzeit nicht als Ereignis gesendet. Rufe
getInfo()auf, wenn du einen aktuellen Wert benötigst, z. B. wenn der Bildschirm den Fokus erhält. - Ausstehende Antworten laufen auf der Smartphone-Seite nach fünf Minuten ab.
Welchen Kanal verwenden
| Benötigt | Nutzt | Warum |
|---|---|---|
| Live-Button-Press, Watch-App geöffnet | sendMessage / /capgo/message |
Schnell, aber geht verloren, wenn kein Node verbunden ist |
| Was die Watch anzeigen soll | updateApplicationContext / /capgo/context |
Letzter Wert wird beibehalten und später synchronisiert |
| Events, die nicht verloren gehen dürfen | transferUserInfo / /capgo/userinfo/{uuid} |
Jedes Event ist ein eigenes DataItem |
Limits des Data Layer
- DataItems sind auf ca. 100 KB begrenzt. Nachrichten sollten ebenfalls klein bleiben. Sende IDs und lade Inhalte von deinem Backend.
- Das Schreiben eines DataItems mit identischem Inhalt löst kein Change-Event aus. Füge einen Timestamp hinzu, wenn du ein frisches Event benötigst.
- Eine Nachricht an die Watch startet deine
WearableListenerService, nicht deine UI. - Am Telefon existieren die Listener des Plugins, solange der Capacitor-App-Prozess läuft. Nachrichten, die gesendet werden, während er nicht aktiv ist, werden nicht an JavaScript weitergegeben. DataItems verbleiben in der Data Layer, daher Kontext für alles verwenden, was das Telefon später sehen muss.
- Payloads sind JSON-Objekte. Top-Level-Arrays und nicht-JSON-Werte werden nicht geparst.
Testen mit Emulatoren und Geräten
- Erstellen Sie in der Device Manager ein Smartphone-Emulator mit einem Google Play-Systemimage sowie einen Wear OS-Emulator.
- Verwende den Wear-OS-Pairing-Assistenten in Android Studio, um sie zu koppeln. Er installiert die Companion-App auf dem Smartphone-Emulator.
- Führe die
wearKonfiguration auf dem Watch-Emulator aus und starte dann deine Capacitor-App auf dem Smartphone mitbunx cap run android. - Rufe
CapgoWatch.getInfo()vom Smartphone aus.isWatchAppInstalled: truebestätigt die Übereinstimmung von Capability und Paket.
Aktiviere auf einer physischen Uhr die Entwickleroptionen und ADB-Debugging über Wi-Fi, verbinde sie dann mit adb connect <watch-ip>:<port>.
Fehlerbehebung
isSupported: false. Google Play-Dienste oder die Wear OS API fehlen auf dem Telefon. Häufig bei Emulatoren ohne Play-Images.
isWatchAppInstalled: false während eine Uhr verbunden ist. Der capgo_watch Die Capability fehlt, ist falsch geschrieben oder die Wear-App ist auf der Uhr nicht installiert.
Nachrichten kommen nie auf der Uhr an. Unterschiedlich applicationId oder unterschiedliche Signing Keys. Vergleiche ./gradlew :app:signingReport und ./gradlew :wear:signingReport.
Context-Updates kommen nicht mehr an. Du schreibst denselben Payload immer wieder. Füge ein sich änderndes Feld hinzu.
Release-Build funktioniert im Debug, aber nicht über Play. Wenn du Play App Signing nutzt, müssen sowohl die Phone- als auch die Watch-Artifacts mit demselben App-Signing-Key in derselben Play Console App signiert sein.
R8 entfernt deinen Service. Behalte Klassen, die nur im Manifest referenziert werden, wenn du eigene Shrinking-Rules verwendest.
Veröffentlichung auf Google Play
Die Wear OS App wird unter derselben Play Console App wie die Phone App veröffentlicht, da sie sich den Package-Namen teilen. Füge den Wear OS Formfaktor in der Play Console hinzu, lade das Wear-Bundle in einen Wear OS Release Track hoch und füge Watch-Screenshots hinzu. Google prüft Wear OS Apps anhand der Wear OS Qualitätsrichtlinien, achte also auf Layouts für runde Displays und die Größen der Touch-Targets.
Für Updates Capgo Live Updates kann Fixes in den Web-Layer der Phone App pushen, ohne Store-Release, was Bugs in deiner TypeScript-Bridge code abdeckt. Kotlin-Änderungen auf der Watch laufen über Google Play. Versioniere deine JSON-Payloads, damit ein neueres Phone-Bundle mit einem älteren Watch-Build kompatibel bleibt. Wenn du die Phone App in der Cloud buildest, Capgo Build übernimmt den Capacitor Android-Build und das Signing, und du kannst einen passenden Keystore mit dem Android Keystore Generator.
Fazit
Ein Wear-OS-Companion für eine Capacitor-App besteht aus drei Bausteinen: @capgo/capacitor-watch auf dem Smartphone, ein Kotlin-Modul, das die App-ID und den Signierungsschlüssel des Telefons teilt, und die /capgo/* Data-Layer-Pfade dazwischen. Erst die Capability und die Signierung richtig konfigurieren, dann ist der code kurz.