Erstellen einer Wear OS-App
Kopiere einen Setup-Prompt mit den Installationsschritten und dem vollständigen Markdown-Guide für dieses Plugin.
Diese Anleitung zeigt dir, wie du eine Wear OS Companion-App erstellst, die über dieselben Data Layer-Pfade mit deiner Capacitor Phone-App kommuniziert wie CapgoWatchPlugin auf Android (play-services-wearable).
Voraussetzungen
Abschnitt: „Voraussetzungen“Before you begin, ensure you have:
- Android Studio (neueste stabile Version)
- Ein bestehendes Capacitor Android-Projekt (
npx cap add androidif needed) - Eine physische Wear OS Uhr, die mit dem Phone gekoppelt ist (empfohlen für End-to-End-Tests)
- Derselbe Signing Key für die Phone-App und die Wear OS Modul Release-Builds
So wird das Plugin auf Wear OS gemappt
Abschnitt mit dem Titel „Mapping des Plugins auf Wear OS“Das Plugin auf dem Smartphone (CapgoWatchPlugin.java) nutzt diese Data-Layer-Routen:
| Route | Typ | Zweck |
|---|---|---|
/capgo/message | Nachricht | Einweg-Nachrichten (vom Phone zur Watch und umgekehrt) |
/capgo/message/withreply | Nachricht | Watch-zu-Phone-Nachrichten mit erwarteter JS-Antwort über replyToMessage |
/capgo/reply/{callbackId} | Nachricht | Phone-Antwort-Payload zurück an die Watch |
/capgo/context | DataItem | Aktueller App-Kontext (payload String enthält JSON) |
/capgo/userinfo/{uuid} | DataItem | Wartende Benutzerinfo (payload String enthält JSON) |
Das Telefon erkennt deine Watch-App mit der capgo_watch Capability (WATCH_APP_CAPABILITY im Plugin).
Projektstruktur
Abschnitt mit dem Titel „Projektstruktur“Nach diesem Guide sieht dein Android-Verzeichnis so aus:
Verzeichnisandroid/
Verzeichnisapp/ (Phone-Modul, Capacitor Host)
- …
Verzeichniswear/ (neues Wear OS Modul)
Verzeichnissrc/main/
- AndroidManifest.xml
- java/…/WearMainActivity.kt
- java/…/CapgoWearBridge.kt
- res/values/wear.xml
- settings.gradle
Schritt 1: Wear OS Modul hinzufügen
Abschnitt mit dem Titel „Schritt 1: Wear OS Modul hinzufügen“-
Öffne den
androidOrdner in Android Studio (nicht das Repo-Root). -
Wähle Datei → Neu → Neues Modul…
-
Wähle Wear OS → Leere Wear-App erstellen (oder Wear OS App mit Compose, falls gewünscht).
-
Konfiguriere das Modul:
- Modulname:
wear(oder dein bevorzugter Name) - Package-Name: sollte idealerweise dem Namespace deiner Phone-App entsprechen
- Mindestmaß an SDK: passend zu deinem Projekt (Plugin
minSdkist 24)
- Modulname:
-
Klicken Fertigstellen und lass den Gradle-Sync durchlaufen.
Schritt 2: Gradle-Abhängigkeiten
Abschnitt mit dem Titel „Schritt 2: Gradle-Abhängigkeiten“Das Capacitor Plugin hängt bereits von com.google.android.gms:play-services-wearable:18.2.0ab. Deine wear Modul benötigt das gleiche API.
Unter wear/build.gradle (oder wear/build.gradle.kts), add:
dependencies { implementation "com.google.android.gms:play-services-wearable:18.2.0" // Your UI dependencies (Compose, etc.)}Gradle nach der Bearbeitung synchronisieren.
Stelle sicher, settings.gradle enthält das wear-Modul:
include ':app'include ':wear'Ausführen npx cap sync android aus deinem Capacitor Projekt, wenn du native Module änderst, und führe dann einen Rebuild in Android Studio durch.
Schritt 3: Die capgo_watch Capability
Abschnitt mit dem Titel „Schritt 3: Die capgo_watch 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"> <string-array name="android_wear_capabilities" translatable="false" tools:ignore="UnusedResources"> <item>capgo_watch</item> </string-array></resources>Dadurch kann das Phone-Plugin festlegen, isWatchAppInstalled wenn es die Capability abfragt capgo_watch.
Schritt 4: Wear-Modul-Manifest und Application ID
Abschnitt mit dem Titel „Schritt 4: Wear-Modul-Manifest und Application ID“Verwenden Sie dieselbe applicationId in der Phone-App und im Wear-Modul. Setzen Sie diese in wear/build.gradle (und gleichen Sie sie in android/app/build.gradle):
android { defaultConfig { applicationId "com.example.myapp" // must match android/app }}Beispiel wear/src/main/AndroidManifest.xml Skeleton:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.type.watch" />
<application android:allowBackup="true" android:label="@string/app_name" android:supportsRtl="true" android:theme="@android:style/Theme.DeviceDefault">
<activity android:name=".WearMainActivity" android:exported="true" android:taskAffinity="" android:theme="@android:style/Theme.DeviceDefault"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>Schritt 5: Wear-side Bridge (Kotlin)
Abschnitt mit dem Titel „Schritt 5: Wear-side Bridge (Kotlin)“Erstellen CapgoWearBridge.kt in deinem Wear-Package. Es spiegelt die Pfade in CapgoWatchPlugin.java.
Das Phone-Plugin weist eine opaque callbackId für jedes /capgo/message/withreply und sendet die Antwort über /capgo/reply/{callbackId}. Die Watch sieht diese ID niemals im Voraus. Nutze für parallele Requests einen requestId in deinem JSON-Payload, gib es vom Phone aus mit replyToMessage, und triggere die Watch-Handler über dieses Feld.
package com.example.myapp.wear
import android.content.Contextimport android.net.Uriimport android.util.Logimport com.google.android.gms.wearable.CapabilityClientimport com.google.android.gms.wearable.DataClientimport com.google.android.gms.wearable.DataEventimport com.google.android.gms.wearable.DataEventBufferimport com.google.android.gms.wearable.DataMapItemimport com.google.android.gms.wearable.MessageClientimport com.google.android.gms.wearable.MessageEventimport com.google.android.gms.wearable.PutDataMapRequestimport com.google.android.gms.wearable.Wearableimport org.json.JSONObjectimport java.nio.charset.StandardCharsetsimport java.util.UUIDimport java.util.concurrent.ConcurrentHashMapimport java.util.concurrent.Executorsimport java.util.concurrent.TimeUnit
class CapgoWearBridge(private val context: Context) : MessageClient.OnMessageReceivedListener, DataClient.OnDataChangedListener {
companion object { private const val TAG = "CapgoWearBridge" const val CAPABILITY = "capgo_watch" const val PATH_MESSAGE = "/capgo/message" const val PATH_MESSAGE_WITH_REPLY = "/capgo/message/withreply" const val PATH_REPLY_PREFIX = "/capgo/reply/" const val PATH_CONTEXT = "/capgo/context" const val PATH_USER_INFO_PREFIX = "/capgo/userinfo/" }
private val executor = Executors.newSingleThreadExecutor() private val timeoutScheduler = Executors.newSingleThreadScheduledExecutor() private val messageClient = Wearable.getMessageClient(context) private val dataClient = Wearable.getDataClient(context) private val nodeClient = Wearable.getNodeClient(context)
var onMessage: ((JSONObject) -> Unit)? = null var onContext: ((JSONObject) -> Unit)? = null var onUserInfo: ((JSONObject) -> Unit)? = null
fun start() { messageClient.addListener(this) dataClient.addListener(this) Wearable.getCapabilityClient(context) .addLocalCapability(CAPABILITY) }
fun stop() { messageClient.removeListener(this) dataClient.removeListener(this) pendingReplyTimeouts.values.forEach { it.cancel(false) } pendingReplyTimeouts.clear() pendingReplyHandlers.clear() timeoutScheduler.shutdownNow() }
fun sendMessage(data: JSONObject, onError: (String) -> Unit = {}) { executor.execute { try { val nodes = com.google.android.gms.tasks.Tasks.await(nodeClient.connectedNodes) val payload = data.toString().toByteArray(StandardCharsets.UTF_8) for (node in nodes) { messageClient.sendMessage(node.id, PATH_MESSAGE, payload).let { com.google.android.gms.tasks.Tasks.await(it) } } } catch (e: Exception) { Log.e(TAG, "sendMessage failed", e) onError(e.message ?: "sendMessage failed") } } }
private val pendingReplyHandlers = ConcurrentHashMap<String, (JSONObject) -> Unit>() private val pendingReplyTimeouts = ConcurrentHashMap<String, java.util.concurrent.ScheduledFuture<*>>() private val outboundUserInfoPaths = ConcurrentHashMap.newKeySet<String>()
fun sendMessageWithReply( data: JSONObject, onReply: (JSONObject) -> Unit, onError: (String) -> Unit = {}, ) { executor.execute { val requestId = UUID.randomUUID().toString() try { val nodes = com.google.android.gms.tasks.Tasks.await(nodeClient.connectedNodes) if (nodes.isEmpty()) { onError("No connected phone node") return@execute } val payloadJson = JSONObject(data.toString()).apply { put("requestId", requestId) } pendingReplyHandlers[requestId] = onReply val timeout = timeoutScheduler.schedule( { if (pendingReplyHandlers.remove(requestId) != null) { pendingReplyTimeouts.remove(requestId) onError("Reply timed out after 5 minutes") } }, 5, TimeUnit.MINUTES, ) pendingReplyTimeouts[requestId] = timeout val node = nodes.first() com.google.android.gms.tasks.Tasks.await( messageClient.sendMessage( node.id, PATH_MESSAGE_WITH_REPLY, payloadJson.toString().toByteArray(StandardCharsets.UTF_8), ), ) } catch (e: Exception) { pendingReplyHandlers.remove(requestId) pendingReplyTimeouts.remove(requestId)?.cancel(false) Log.e(TAG, "sendMessageWithReply failed", e) onError(e.message ?: "sendMessageWithReply failed") } } }
fun updateApplicationContext(contextData: JSONObject, onError: (String) -> Unit = {}) { executor.execute { try { val request = PutDataMapRequest.create(PATH_CONTEXT) request.dataMap.putString("payload", contextData.toString()) request.setUrgent() com.google.android.gms.tasks.Tasks.await(dataClient.putDataItem(request.asPutDataRequest())) } catch (e: Exception) { Log.e(TAG, "updateApplicationContext failed", e) onError(e.message ?: "updateApplicationContext failed") } } }
fun transferUserInfo(userInfo: JSONObject, onError: (String) -> Unit = {}) { executor.execute { val path = PATH_USER_INFO_PREFIX + UUID.randomUUID() try { outboundUserInfoPaths.add(path) val request = PutDataMapRequest.create(path) request.dataMap.putString("payload", userInfo.toString()) request.setUrgent() com.google.android.gms.tasks.Tasks.await(dataClient.putDataItem(request.asPutDataRequest())) } catch (e: Exception) { outboundUserInfoPaths.remove(path) Log.e(TAG, "transferUserInfo failed", e) onError(e.message ?: "transferUserInfo failed") } } }
override fun onMessageReceived(event: MessageEvent) { val path = event.path val json = try { JSONObject(String(event.data, StandardCharsets.UTF_8)) } catch (e: Exception) { Log.w(TAG, "Ignoring non-JSON message on $path", e) return }
when { path == PATH_MESSAGE -> onMessage?.invoke(json) path.startsWith(PATH_REPLY_PREFIX) -> { val requestId = json.optString("requestId", "") if (requestId.isNotEmpty()) { pendingReplyTimeouts.remove(requestId)?.cancel(false) pendingReplyHandlers.remove(requestId)?.invoke(json) } } } }
override fun onDataChanged(dataEvents: DataEventBuffer) { for (event in dataEvents) { if (event.type != DataEvent.TYPE_CHANGED) continue val item = event.dataItem val path = item.uri.path ?: continue val map = DataMapItem.fromDataItem(item).dataMap val payload = map.getString("payload", "{}") val json = try { JSONObject(payload) } catch (e: Exception) { Log.w(TAG, "Ignoring non-JSON DataItem on $path", e) continue }
when { path == PATH_CONTEXT -> onContext?.invoke(json) path.startsWith(PATH_USER_INFO_PREFIX) -> { if (outboundUserInfoPaths.remove(path)) { continue } onUserInfo?.invoke(json) dataClient.deleteDataItems(item.uri) } } } }
}Verbinde die Bridge in WearMainActivity.kt:
class WearMainActivity : ComponentActivity() { private lateinit var bridge: CapgoWearBridge
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) bridge = CapgoWearBridge(applicationContext) bridge.onMessage = { json -> Log.d("Wear", "phone message: $json") } bridge.onContext = { json -> Log.d("Wear", "context: $json") } bridge.onUserInfo = { json -> Log.d("Wear", "userInfo: $json") } bridge.start() setContent { /* your Compose UI */ } }
override fun onDestroy() { bridge.stop() super.onDestroy() }}Nutzt applicationContext beim Erstellen von Clients, damit Listener Konfigurationsänderungen überstehen.
Schritt 6: Phone-seitiges Capacitor (TypeScript)
Abschnitt mit dem Titel „Schritt 6: Phone-seitiges Capacitor (TypeScript)“Installiere und synchronisiere das Plugin in der Phone-App (wie bei iOS):
import { CapgoWatch } from '@capgo/capacitor-watch';
export async function setupWatchListeners() { await CapgoWatch.addListener('messageReceived', (event) => { console.log('Wear message:', event.message); });
await CapgoWatch.addListener('messageReceivedWithReply', async (event) => { const requestId = event.message.requestId; await CapgoWatch.replyToMessage({ callbackId: event.callbackId, data: { requestId, ok: true, echo: event.message }, }); });
await CapgoWatch.addListener('applicationContextReceived', (event) => { console.log('Wear context:', event.context); });
await CapgoWatch.addListener('userInfoReceived', (event) => { console.log('Wear userInfo:', event.userInfo); });}
export async function pingWatch() { const info = await CapgoWatch.getInfo(); if (!info.isSupported) { console.warn('Wear OS APIs not available on this device'); return; } if (!info.isReachable) { console.warn('No connected Wear OS nodes'); return; } await CapgoWatch.sendMessage({ data: { action: 'ping', at: Date.now() }, });}Rufe setupWatchListeners() frühzeitig beim App-Bootstrap (zum Beispiel nach platform.ready()).
Schritt 7: Build, Installation und Test
Abschnitt mit dem Titel „Schritt 7: Build, Installation und Test“-
Wähle die Smartphone
appRun-Konfiguration, Installation auf einem physischen Android-Smartphone. -
Wähle die
wearRun-Konfiguration, Installation auf der gekoppelten Watch (oder einem Watch-Emulator, der mit dem Phone-Emulator verbunden ist). -
Bestätigen
getInfo()auf dem Smartphone:isSupported: truewenn Google Play Services und Wearable API verfügbar sindisReachable: truewenn mindestens ein Node verbunden istisWatchAppInstalled: truewenn die Watch das Signal sendetcapgo_watch
-
Senden
CapgoWatch.sendMessagevom Phone aus und verifizierenbridge.onMessageauf der Watch. -
Von der Watch aus senden mit
sendMessageWithReplyund bestätigenmessageReceivedWithReplyplusreplyToMessagewird geliefert auf/capgo/reply/{callbackId}. -
Test
updateApplicationContextundtransferUserInfoin beide Richtungen über die oben genannten DataItem-Pfade.
Fehlerbehebung
Abschnitt mit dem Titel „Troubleshooting“isSupported: false auf einem echten Smartphone
- Google Play Services fehlen oder sind veraltet
- Wearable API auf dem Gerät nicht verfügbar
isReachable: false aber die Uhr ist gekoppelt
- Bluetooth deaktiviert oder Wear-App wurde nicht kürzlich gestartet
- Unterschiedlich
applicationIdoder Signing-Keys zwischen den Modulen - Kein Wear OS Node aktuell mit Google Play Services verbunden
isWatchAppInstalled: false
- Wear Companion APK nicht auf der Uhr installiert
- Fehlt
capgo_watchEintrag inwear.xml - Capability nicht synchronisiert: Wear APK nach Änderung der Capabilities neu installieren
Nachrichten kommen nie an
- Pfad muss exakt übereinstimmen (
/capgo/message, kein benutzerdefinierter Präfix) - Payload muss JSON-Text in UTF-8 sein (das Plugin nutzt
JSONObject/JSObjectStrings)
Antwort wird nie an die Watch zurückgegeben
- Das Phone muss aufrufen
replyToMessagemit demcallbackIdvonmessageReceivedWithReply - Dasselbe einbinden
requestIdim Reply-JSON enthalten, falls die Watch parallele Requests korreliert - Ausstehende Antworten laufen nach 5 Minuten ab auf Android (Plugin-Konstante
PENDING_REPLY_TTL_MS)
Nächste Schritte
Abschnitt mit dem Titel „Nächste Schritte“- Erstellen einer watchOS-App für Apple Watch und CapgoWatchSDK
- Erste Schritte für gemeinsame API und Plattform-Hinweise
- Beispiele für vollständige App-Patterns
- Plugin-Source für Pfadkonstanten auf der Phone-Seite
Weiterlesen: Erstellen einer Wear OS-App
Abschnitt mit dem Titel „Weiterlesen: Erstellen einer Wear OS-App“Wenn du es nutzt Erstellen einer Wear OS-App Um die Arbeit an nativen Plugins zu planen, verbinde sie mit Verwendung von @capgo/capacitor-watch für die native Funktion in „Verwendung von @capgo/capacitor-watch“, Capgo Plugin-Verzeichnis für den Produkt-Workflow im Capgo Plugin-Verzeichnis, Capacitor Plugins von Capgo für das Implementierungsdetail in Capacitor Plugins von Capgo, Plugins hinzufügen oder aktualisieren für das Implementierungsdetail beim Hinzufügen oder Aktualisieren von Plugins, und Ionic Enterprise Plugin-Alternativen für den Produkt-Workflow bei den Ionic Enterprise Plugin-Alternativen.