Connecting watchOS and Wear OS Companion Apps | Mironsoft
AI generated
RN
native
React Native / Wearables
watchOS and Wear OS Companion Apps
connecting native wearable targets to React Native

React Native does not render on the Apple Watch or a Wear OS watch, watch apps are always standalone native targets. This article covers the communication strategy through WatchConnectivity and the Wearable Data Layer API, plus the realistic limits of code reuse.

13 min read WatchConnectivity Wear Data Layer Native Targets

1. A realistic framing: React Native does not render on the watch

A common misconception in React Native projects is that you can simply bring the existing app to the Apple Watch or a Wear OS watch by running the React Native renderer there as well. Technically, that is out of the question: neither watchOS nor Wear OS provide a runtime environment where a phone app's JavaScript engine and React Native renderer could execute. A watch app is always a standalone, fully native application with its own app target, its own lifecycle, and its own noticeably tighter memory and compute budget.

That does not mean React Native teams cannot build a watch connection, it means the watch app has to be treated architecturally as a separate project that exchanges data with the React Native phone app through defined communication channels. Realistic expectations about scope and effort matter here: a watch companion app typically shows a heavily reduced subset of the phone app's functionality, not a full parallel implementation.

2. watchOS companion architecture: a standalone SwiftUI target

A watchOS app is created as an additional target in the same Xcode project that contains the iOS app, but runs as its own process on its own runtime and is implemented entirely in SwiftUI. Since more recent watchOS versions, standalone watch apps without a mandatory iOS companion app are possible, but for a React Native companion app the classic model remains relevant, where the watch app ships as an extension to the existing iOS app under the same App Store listing.

Important for planning: the watchOS app does not share process memory with the iOS app, every data transfer runs explicitly through one of Apple's dedicated communication APIs, there is no direct function call from the watch app into the React Native JavaScript context of the phone app. This isolation is deliberate, so the watch app keeps working even when the iPhone is not currently in range or the phone app is not actively loaded in memory.

3. Wear OS companion architecture: Kotlin and Jetpack Compose for Wear

On the Android side, a Wear OS app runs as a separate APK inside the same app bundle, implemented in Kotlin with Jetpack Compose for Wear OS as the current UI framework, while older projects sometimes still use classic view-based wear layouts. Wear OS additionally distinguishes between a full-fledged app running standalone on the watch and lighter-weight formats like tiles for quick access or complications for watch face integration, each bringing its own, even more constrained APIs.

Similar to watchOS, a Wear OS app runs as its own process, regardless of whether the paired smartphone is currently reachable. Google also supports standalone wear apps that work on the watch even without a companion smartphone app, establishing connectivity directly over the watch's own WiFi or LTE, though that is usually not the relevant use case for a pure companion app to a React Native phone app.

4. WatchConnectivity: communication channels between iPhone and Apple Watch

The WatchConnectivity framework provides several transfer paths through a WCSession instance, differing in urgency and reliability. sendMessage delivers data in real time, but requires the counterpart to be currently reachable, otherwise the call fails. For data that needs to arrive reliably even when the counterpart is unreachable, transferUserInfo works better, queuing the transfer and automatically retrying once the connection is restored.

For the most common case in companion apps, keeping the current overall state in sync, say the most recently known order status, updateApplicationContext is usually the right choice: it overwrites earlier, not-yet-delivered context updates, so only the most current state is ever transmitted instead of working through a queue of stale intermediate states. These three APIs complement each other, and picking the right one depends on the specific data type and the freshness required.


// ios/WatchBridge.swift
import WatchConnectivity
import ExpoModulesCore

public class WatchBridge: Module {
  public func definition() -> ModuleDefinition {
    Name("WatchBridge")

    Function("syncState") { (payload: [String: Any]) in
      guard WCSession.default.activationState == .activated else { return }
      try WCSession.default.updateApplicationContext(payload)
    }
  }
}

5. Wear OS Data Layer API: MessageClient, DataClient, and CapabilityClient

The Android counterpart is called the Wearable Data Layer API and offers, with MessageClient, an analog to sendMessage for one-off, near-real-time messages between phone and watch. For persistent, automatically synced data objects that stay consistent even across connection drops, DataClient is the right API, comparable to updateApplicationContext on watchOS, just with more granular control over individual data paths instead of a single overall context.

Additionally, CapabilityClient determines which connected devices currently offer which capabilities, for example whether a Wear OS app is even installed on a paired watch, before attempting to send data there. This reachability check matters in practice, because otherwise a React Native app might try to send data to a device that does not have the companion app installed at all, which then either fails silently or forces unnecessary error handling in the JavaScript layer.


// android/src/main/java/com/example/watch/WatchBridgeModule.kt
package com.example.watch

import com.google.android.gms.wearable.PutDataMapRequest
import com.google.android.gms.wearable.Wearable
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition

class WatchBridgeModule : Module() {
  override fun definition() = ModuleDefinition {
    Name("WatchBridge")

    AsyncFunction("syncState") { orderStatus: String ->
      val context = appContext.reactContext ?: return@AsyncFunction
      val request = PutDataMapRequest.create("/order-status").apply {
        dataMap.putString("status", orderStatus)
      }
      Wearable.getDataClient(context).putDataItem(request.asPutDataRequest())
    }
  }
}

6. The bridge: a native module between React Native and watch APIs

The practical connection runs through a native module inside the React Native app that implements the WCSession delegate methods on iOS and wraps MessageClient or DataClient on Android, each exposing a thin JavaScript interface for sending and receiving state changes. If, say, the cart contents or the order status change inside the React Native app, the business logic calls a function like WatchBridge.syncState(payload), which internally talks to the matching native API.

In the other direction, from the watch app back to the React Native app, the same native module receives incoming messages through the respective delegate callback method and forwards them to the JavaScript layer via an event emitter, where they are processed like a regular app event, such as a button press or a user action on the watch. This two-way bridge has to be implemented separately for each platform, since the underlying APIs are conceptually similar but technically completely different.


// app/services/watchBridge.ts
import { requireNativeModule } from "expo-modules-core";

const WatchBridge = requireNativeModule("WatchBridge");

export function syncOrderStatusToWatch(status: string): void {
  WatchBridge.syncState({ status });
}

7. Practical example: order status and quick reorder on the watch

A realistic use case for a shop with a React Native app is a watch view showing the status of the most recently placed order, such as Shipped, Out for Delivery, or Delivered, together with a single button for a quick reorder of the last order. The actual order logic, including payment processing, deliberately stays on the phone, the watch merely triggers a request that gets forwarded through the bridge to the React Native app and processed there in full.

This deliberate feature reduction is typical of successful watch companion apps: instead of trying to replicate the full checkout flow on a small screen with heavily constrained input, the watch app limits itself to fast status checks and a single, clearly defined action, while all more complex interactions continue to run through the phone, where a keyboard, a bigger screen, and the full React Native surface are all available.

8. Limits of code reuse

The UI layer fundamentally cannot be shared between the React Native app and the watch app, neither components nor styling logic, because SwiftUI and Jetpack Compose for Wear share nothing conceptually or technically with React Native components. What is reusable, on the other hand, is pure data models and simple business rules, provided they are moved into a platform-specific Swift or Kotlin package that is independent of the React Native bridge and consumed both by the phone app's bridge and by the watch app.

In practice, this means for most teams: one shared Swift package with pure data structures and validation logic for the iOS side, a corresponding Kotlin module for the Android side, but four separate UI implementations overall, React Native for the phone, SwiftUI for the Apple Watch, Jetpack Compose for Wear OS, and possibly a shared API layer on the server that serves all four clients equally.

9. Testing and shipping the watch companion app

A watchOS app is built and tested through its own scheme in Xcode, either in the watch simulator paired with the iOS simulator or on a real Apple Watch paired with the test iPhone. For App Store release, the watch app is submitted as part of the same App Store listing as the iOS app, but has to provide its own screenshots and meet partly separate review criteria, particularly around meaningful standalone value of the watch features.

On the Android side, the Wear OS app is built as an additional APK inside the same Android App Bundle and distributed through the Play Console, tested first in the Wear OS emulator and later on real hardware through the same internal test track infrastructure as the phone app. Both platforms require the watch app to show at least a sensible empty state when there is no network connection to the phone app, instead of appearing completely blank or crashing.

Aspect watchOS Wear OS
UI framework SwiftUI Jetpack Compose for Wear OS
Communication API WatchConnectivity (WCSession) Wearable Data Layer API
Real-time message sendMessage MessageClient
Persistent state updateApplicationContext DataClient
Standalone operation Possible since more recent watchOS versions Standalone apps officially supported

Mironsoft

React Native app development and Magento integration

A mobile app for the Magento shop that actually runs smoothly?

We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.

App Concept

Plan the architecture and feature scope of a Magento-connected app together.

Magento API Integration

Cleanly connect product catalog, cart, and checkout to the shop API.

Store Publishing

Guide the App Store and Google Play release process without pitfalls.

10. Summary

watchOS and Wear OS Companion Apps: The Essentials at a Glance

Core takeaway

React Native does not render on the watch, watch apps are always standalone native targets.

Communication

WatchConnectivity on iOS, the Wearable Data Layer API on Wear OS.

Reusable

Pure data models and business rules through platform-specific shared packages.

Not reusable

The entire UI layer, SwiftUI and Jetpack Compose stay separate.

11. FAQ: watchOS and Wear OS Companion Apps: The Essentials at a Glance

1Can I run my React Native app directly on the Apple Watch?
No, neither watchOS nor Wear OS provide a runtime environment for the React Native JavaScript engine, a watch app always has to be built fully natively.
2Which framework do you use for a watchOS companion app?
SwiftUI, as a standalone Xcode target inside the same project as the iOS app.
3How do the iPhone app and the watch app exchange data?
Through the WatchConnectivity framework using a WCSession instance, which uses sendMessage, transferUserInfo, or updateApplicationContext depending on the requirement.
4What is the difference between sendMessage and updateApplicationContext?
sendMessage delivers real-time data but requires a reachable counterpart, while updateApplicationContext reliably transmits the latest state and overwrites older, not-yet-delivered updates.
5How does communication work on Wear OS?
Through the Wearable Data Layer API with MessageClient for one-off messages and DataClient for persistent, automatically synced data objects.
6Can I share UI code between the React Native app and the watch app?
No, the UI layer on the watch always has to be implemented fully natively in SwiftUI or Jetpack Compose for Wear.
7What can be reused across the platforms?
Pure data models and business rules inside a platform-specific Swift or Kotlin package, but not UI components or styling.
8Does a Wear OS app strictly need a paired smartphone?
Not strictly, Google also supports standalone wear apps with their own connectivity, but for a pure companion app the paired variant is usually the relevant one.
9How do you check whether a watch app is even installed before sending data?
Through CapabilityClient on Wear OS or the isWatchAppInstalled property of WCSession on watchOS.
10Where does the actual order logic run for a watch reorder feature?
Still entirely on the phone inside the React Native app, the watch app merely triggers the request through the bridge.