Allez directement au contenu principal

Comment créer une application Wear OS pour votre application Capacitor

Créez une application Wear OS pour votre application Capacitor : ajoutez un module Wear OS, écrivez le côté montre en Kotlin et synchronisez les données sur la couche de données avec @capgo/capacitor-montre.

Comment créer une application Wear OS pour votre application Capacitor

Pour créer une application Wear OS pour votre application Capacitor, ajoutez un module natif Wear OS à la android/ projet, donnez-lui le même ID d'application et la même clé de signature que l'application pour téléphone, et connectez les deux avec @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.

Cette guide vous guide à travers la configuration du module, les chemins de la couche de données exacts que le plugin utilise, le Kotlin code sur la montre, le TypeScript code sur le téléphone, le test avec des émulateurs et la publication sur Google Play.

Comment fonctionne le pont Capacitor vers Wear OS ?

Wear OS n'a pas de runtime Capacitor, donc l'application de montre est native. Le plugin parle la couche de données API.play-services-wearable) sur le téléphone, et votre application de la montre parle le même protocole.

Méthode du plugin / événement Mécanisme de la couche de données Chemin
sendMessage (téléphone vers montre) MessageClient /capgo/message
messageReceived (montre vers téléphone) 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}

Les messages transportent des JSON UTF-8 sous forme de bytes bruts. Les DataItems transportent le même JSON sous forme de chaîne sous la clé DataMap payloadLe téléphone vérifie l'existence d'une application montre en recherchant la capacité. capgo_watch.

Connaître ces chemins est la clé. Une fois que la montre les lit et les écrit, chaque méthode de plugin fonctionne.

Ce dont vous avez besoin

  • Un Capacitor 8 avec la plateforme Android ajoutée (guide d'upgrade si vous êtes en retard).
  • Un Android Studio récent avec les images système Wear OS installées.
  • Un téléphone avec Google Play services. Le Data Layer en dépend, donc les téléphones sans Play services signalent isSupported: false.
  • Un montre Wear OS ou émulateur associé à un téléphone Android. Les montres Wear OS associées à un iPhone ne peuvent pas rejoindre votre application.

Étape 1 : Installer le plugin

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

Le même package gère l'Apple Watch sur iOS, donc une application cross-platform nécessite une seule importation. Voir les docs du plugin pour la référence complète, et notre guide Apple Watch pour le côté iOS.

Étape 2 : Ajoutez un module Wear OS au projet Android

Open android/ dans Android Studio (bunx cap open androidet utilisez Fichier > Nouveau > Nouveau Module > 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.

Le magicien ajoute include ':wear' to android/settings.gradleConservez-les, et laissez les lignes gérées par Capacitor intactes.

Deux règles que la couche de données impose

  1. Même applicationId. En wear/build.gradle, définissez applicationId à exactement la valeur dans android/app/build.gradleLe namespace peut différer, l'ID de l'application ne peut pas.
  2. Même certificat de signature. Les builds de débogage sur une machine partagent le keystore de débogage, donc cela fonctionne généralement localement et puis se brise en version de production. Signez la build de débogage Wear OS avec la même clé de téléversement que l'application du téléphone.

Si l'une des règles est brisée, les deux applications ne se verront jamais et rien dans votre code ne pourra les rétablir.

Dépendances et minimum SDK

Wear OS 3 et ultérieurs sont basés sur API 30, donc minSdk 30 is une plateforme pratique pour une nouvelle application montre. Ajoutez la couche de données et les aides de 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
}

Déclarez la capacité

Créer 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 chaîne doit être capgo_watch, car c'est ce que le plugin interroge pour calculer isWatchAppInstalled.

Marquez l'application comme une application montre

In wear/src/main/AndroidManifest.xml, le guide ajoute <uses-feature android:name="android.hardware.type.watch" />. Décidez si l'application fonctionne sans téléphone. Un compagnon qui ne montre que les données du téléphone doit déclarer :

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

Étape 3 : Recevez des données sur la montre

A WearableListenerService lets the system deliver Data Layer events even when the watch UI is closed. Register it with filters for the /capgo chemins :

<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 petit magasin partagé que l'interface peut observer.

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())
}

Le service analyse chaque chemin :

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
    }
}

When the watch UI opens, also read the current context directly so it shows data that arrived while the app was closed:

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()
    }
}

Étape 4 : Envoyer des données de l'horloge

Le montre envoie des messages à chaque téléphone connecté. Les messages sont pour les interactions en temps réel, les DataItems pour l'état.

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()
    }
}

Lorsque le téléphone reçoit un /capgo/userinfo/... Lorsque le téléphone reçoit un userInfoReceived et supprime l'élément, afin que la file d'attente ne grandisse pas indéfiniment.

Une écran Compose minimal l'unité rassemble :

@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")}")
    }
}

Une écran Compose minimal les relie : 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.

Étape 5 : Parlez à l'horloge à partir de votre Capacitor code

Le côté TypeScript est le même code que vous écririez pour 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' } });
  }
}

Spécificités Android à connaître :

  • sendMessage refuse avec « Aucun appareil Wear OS connecté trouvé » lorsque pas de nœud d'horloge est connecté.
  • isPaired et isReachable les deux signifient « au moins un nœud Wear OS est connecté ». isWatchAppInstalled signifie qu'un nœud annonce capgo_watch.
  • La disponibilité n'est pas envoyée comme événement sur Android aujourd'hui. Appelez getInfo() lorsque vous avez besoin d'une valeur fraîche, par exemple lorsque l'écran gagne le focus.
  • Les réponses en attente expirent après cinq minutes du côté du téléphone.

Quel canal utiliser

Besoin Utilisez Pourquoi
Live button press, watch app open sendMessage / /capgo/message Rapide, mais abandonné si aucun nœud n'est connecté
Ce que la montre doit afficher updateApplicationContext / /capgo/context Latest value is kept and synced later
Événements qui ne doivent pas être perdus transferUserInfo / /capgo/userinfo/{uuid} Chaque événement est son propre DataItem

Limits de la couche de données

  • Les DataItems sont limités à environ 100 Ko. Les messages doivent rester petits aussi. Envoyez des IDs et récupérez le contenu de votre backend.
  • Écrire un DataItem avec le même contenu n'entraîne pas de changement d'événement. Ajoutez une date et heure pour obtenir un événement frais.
  • Un message à la montre démarre votre WearableListenerServicepas votre interface utilisateur.
  • On l'application du téléphone, les écouteurs du plugin existent pendant que le processus d'application Capacitor fonctionne. Les messages envoyés pendant qu'il est mort ne sont pas réexpédiés vers JavaScript. Les DataItems restent dans la couche de données, utilisez donc le contexte pour tout ce que le téléphone doit voir plus tard.
  • Les payloads sont des objets JSON. Les tableaux de niveau supérieur et les valeurs non-JSON ne seront pas parsés.

Testez avec des émulateurs et des appareils

  1. Créez un émulateur de téléphone avec une image système Google Play et un émulateur Wear OS dans Gestionnaire d'appareils.
  2. Utilisez l'assistant de pairing Wear OS dans Android Studio pour les pairer. Cela installe l'application de compagnon sur l'emulateur de téléphone.
  3. Exécutez la wear configuration sur l'émulateur de montre, puis exécutez votre application Capacitor sur le téléphone avec bunx cap run android.
  4. Appelle CapgoWatch.getInfo() à partir du téléphone. isWatchAppInstalled: true confirme la correspondance de capacité et de package.

Activer les options de développeur et la débogage ADB par Wi-Fi sur l'horloge, puis connectez-vous adb connect <watch-ip>:<port>.

Troubleshooting

isSupported: false. Google Play services ou le Wear OS API est manquant sur le téléphone. Fréquent sur les émulateurs sans images Play.

isWatchAppInstalled: false tandis qu'un montre est connecté. Le capgo_watch la capacité est manquante ou mal orthographiée, ou l'application de montre n'est pas installée sur cette montre.

Les messages ne parviennent jamais sur la montre. Different applicationId ou des clés de signature différentes. Comparez ./gradlew :app:signingReport et ./gradlew :wear:signingReport.

Mises à jour de contexte cessent d'arriver. Vous écrivez le même payload plusieurs fois. Ajoutez un champ changeant.

Release build works on debug but not on Play. Si vous utilisez la signature d'application Play, les artefacts du téléphone et de l'horloge doivent être signés par la même clé de signature d'application dans la même application Play Console.

R8 supprime votre service. Keep classes referenced only from the manifest if you use custom shrinking rules.

Publier sur Google Play

L'application Wear OS est livrée sous la même application Play Console que l'application téléphone, car elles partagent le nom de package. Ajoutez la forme facteur Wear OS dans Play Console, téléchargez le bundle Wear OS sur une piste de version Wear OS et ajoutez des captures d'écran d'horloge. Google évalue les applications Wear OS en fonction de ses lignes directrices de qualité Wear OS, donc vérifiez les dispositions d'écran circulaire et les tailles de cibles de toucher.

Pour les mises à jour, pouvez pousser des correctifs vers l'application téléphone web sans une mise à jour de magasin, ce qui couvre les bogues dans votre pont TypeScript Capgo. Les modifications Kotlin sur l'horloge passent par Google Play. Versionnez vos payloads JSON afin qu'un bundle téléphone plus récent reste compatible avec une version horloge plus ancienne. Si vous construisez l'application téléphone dans le cloud, can push fixes to the phone app’s web layer without a store release, which covers bugs in your TypeScript bridge code. Kotlin changes on the watch go through Google Play. Version your JSON payloads so a newer phone bundle stays compatible with an older watch build. If you build the phone app in the cloud, Capgo Création Gère la Capacitor construction et l'authentification Android, et vous pouvez générer un coffre-fort correspondant avec le Générateur de clés de sécurité Android.

Wrap-up

Ainsi, une application Wear OS pour un Capacitor s'articule en trois éléments : @capgo/capacitor-watch sur le téléphone, un module Kotlin qui partage l'ID et la clé de signature de l'application du téléphone, et les /capgo/* Obtenez d'abord la capacité et la signature correctes, puis le code est court.

Mises à jour instantanées pour les applications Capacitor

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

Actualités de notre Blog

Capgo vous offre les meilleures informations nécessaires pour créer une application mobile véritablement professionnelle.