Building Wearable Companion Apps for Apple Watch and Wear OS

Smartwatches are no longer novelties strapped to early adopters’ wrists. Apple Watch dominates with over 50% global market share, and Google’s Wear OS has gained ground significantly since Samsung adopted it for the Galaxy Watch series. For app developers, wearable companion apps represent a genuine opportunity to extend your mobile app’s value to users’ wrists.

But wearable development is a different discipline from phone development. Screens are tiny, interactions are measured in seconds, battery constraints are severe, and the connectivity model between watch and phone introduces complexity that catches many teams off guard. This guide covers the practical realities of building companion apps for both platforms.

Designing for the Wrist

Designing for the Wrist Infographic

Before writing any code, internalise this principle: a wearable app is not a shrunken phone app. The interaction model is fundamentally different.

Glanceable information. Users look at their watch for 2-5 seconds. Your app needs to deliver value in that window. Show the most important information immediately, with no scrolling required for the primary use case.

Quick actions. Wearable interactions should complete in under 10 seconds. If an action takes longer, it belongs on the phone. Common wearable actions: start/stop a timer, confirm a notification, check a status, log a quick data point.

Complications and tiles. The most valuable real estate on a watch is the watch face. Both Apple Watch complications and Wear OS tiles let your app display data without launching the full app.

Information Hierarchy

Design your screens with this priority:

  1. Primary metric: The single most important piece of data (visible immediately)
  2. Supporting context: Secondary information that adds meaning (visible without scrolling)
  3. Actions: One or two tappable actions (visible without scrolling)
  4. Detail: Additional information (accessible by scrolling)

Apple Watch Development with watchOS

Apple Watch Development with watchOS 9 Infographic 9

watchOS 9 provides a mature development environment built on SwiftUI. Apple has fully embraced SwiftUI for watchOS, and the framework handles the tiny screen remarkably well.

Project Setup

In Xcode, add a watchOS target to your existing iOS project. Choose “Watch App” and select “Watch App with Companion iOS App” to create the paired architecture.

Your project structure will include:

MyApp/
  MyApp/           # iOS app
  MyAppWatch/      # watchOS app
    MyAppWatchApp.swift
    ContentView.swift
    ComplicationController.swift
  Shared/          # Shared models and logic

Building the Watch Interface

SwiftUI on watchOS supports most of the same views as iOS, with automatic adaptation for the smaller screen:

struct WorkoutView: View {
    @StateObject private var workoutManager = WorkoutManager()

    var body: some View {
        TabView {
            // Main metrics
            VStack {
                Text(workoutManager.elapsedTime.formatted())
                    .font(.system(.title, design: .monospaced))
                    .foregroundColor(.yellow)

                HStack {
                    MetricView(
                        label: "BPM",
                        value: "\(workoutManager.heartRate)"
                    )
                    MetricView(
                        label: "CAL",
                        value: "\(workoutManager.calories)"
                    )
                }
            }

            // Controls
            VStack {
                Button(action: workoutManager.togglePause) {
                    Image(systemName: workoutManager.isPaused
                        ? "play.fill" : "pause.fill")
                }
                .tint(workoutManager.isPaused ? .green : .yellow)

                Button(role: .destructive, action: workoutManager.end) {
                    Text("End")
                }
            }
        }
        .tabViewStyle(.page)
    }
}

Complications

Complications are the most valuable feature of a watch app. They live on the watch face and update periodically:

struct ComplicationProvider: CLKComplicationDataSource {
    func getCurrentTimelineEntry(
        for complication: CLKComplication,
        withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void
    ) {
        let template: CLKComplicationTemplate

        switch complication.family {
        case .graphicCircular:
            let gauge = CLKSimpleGaugeProvider(
                style: .fill,
                gaugeColor: .green,
                fillFraction: Float(stepsToday) / Float(stepGoal)
            )
            template = CLKComplicationTemplateGraphicCircularClosedGaugeText(
                gaugeProvider: gauge,
                centerTextProvider: CLKSimpleTextProvider(text: "\(stepsToday)")
            )

        case .graphicRectangular:
            template = CLKComplicationTemplateGraphicRectangularTextGauge(
                headerTextProvider: CLKSimpleTextProvider(text: "Steps"),
                body1TextProvider: CLKSimpleTextProvider(
                    text: "\(stepsToday) of \(stepGoal)"
                ),
                gaugeProvider: CLKSimpleGaugeProvider(
                    style: .fill,
                    gaugeColor: .green,
                    fillFraction: Float(stepsToday) / Float(stepGoal)
                )
            )

        default:
            handler(nil)
            return
        }

        let entry = CLKComplicationTimelineEntry(
            date: Date(),
            complicationTemplate: template
        )
        handler(entry)
    }
}

Phone-Watch Communication

WatchConnectivity is the framework for exchanging data between your iOS and watchOS apps:

class ConnectivityManager: NSObject, ObservableObject, WCSessionDelegate {
    static let shared = ConnectivityManager()
    @Published var receivedData: [String: Any] = [:]

    override init() {
        super.init()
        if WCSession.isSupported() {
            WCSession.default.delegate = self
            WCSession.default.activate()
        }
    }

    // Send data to the counterpart app
    func sendMessage(_ data: [String: Any]) {
        guard WCSession.default.isReachable else {
            // Use transferUserInfo for background delivery
            WCSession.default.transferUserInfo(data)
            return
        }
        WCSession.default.sendMessage(data, replyHandler: nil)
    }

    // Receive messages
    func session(
        _ session: WCSession,
        didReceiveMessage message: [String: Any]
    ) {
        DispatchQueue.main.async {
            self.receivedData = message
        }
    }
}

Choose the right communication method:

  • sendMessage: Real-time, requires both apps to be reachable
  • transferUserInfo: Queued delivery, guaranteed but not immediate
  • updateApplicationContext: Latest-value-wins, good for current state
  • transferFile: For larger data payloads

Wear OS Development

Wear OS development uses Jetpack Compose for Wear OS, which shares concepts with regular Compose but adapts to the circular, small-screen form factor.

Project Setup

Add a Wear OS module to your existing Android project:

// wear/build.gradle
dependencies {
    implementation "androidx.wear.compose:compose-material:1.1.2"
    implementation "androidx.wear.compose:compose-foundation:1.1.2"
    implementation "androidx.wear.compose:compose-navigation:1.1.2"
    implementation "androidx.wear:wear:1.3.0"
    implementation "androidx.wear.tiles:tiles:1.1.0"
    implementation "com.google.android.horologist:horologist-compose-layout:0.3.8"
}

Building with Compose for Wear OS

@Composable
fun WorkoutScreen(viewModel: WorkoutViewModel = viewModel()) {
    val state by viewModel.uiState.collectAsState()

    Scaffold(
        timeText = { TimeText() },
        vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) }
    ) {
        ScalingLazyColumn(
            modifier = Modifier.fillMaxSize(),
            anchorType = ScalingLazyListAnchorType.ItemCenter
        ) {
            item {
                Text(
                    text = state.elapsedTime,
                    style = MaterialTheme.typography.display1,
                    color = MaterialTheme.colors.primary
                )
            }

            item {
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.SpaceEvenly
                ) {
                    MetricChip(label = "BPM", value = "${state.heartRate}")
                    MetricChip(label = "CAL", value = "${state.calories}")
                }
            }

            item {
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.SpaceEvenly
                ) {
                    Button(
                        onClick = { viewModel.togglePause() },
                        colors = ButtonDefaults.buttonColors(
                            backgroundColor = if (state.isPaused)
                                Color.Green else MaterialTheme.colors.surface
                        )
                    ) {
                        Icon(
                            imageVector = if (state.isPaused)
                                Icons.Default.PlayArrow else Icons.Default.Pause,
                            contentDescription = "Toggle pause"
                        )
                    }

                    Button(
                        onClick = { viewModel.endWorkout() },
                        colors = ButtonDefaults.buttonColors(
                            backgroundColor = MaterialTheme.colors.error
                        )
                    ) {
                        Icon(
                            imageVector = Icons.Default.Stop,
                            contentDescription = "End workout"
                        )
                    }
                }
            }
        }
    }
}

Tiles

Wear OS Tiles are the equivalent of Apple Watch complications — they appear in the tile carousel and provide glanceable information:

class StepsTileService : TileService() {
    override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
        Futures.immediateFuture(
            Tile.Builder()
                .setResourcesVersion("1")
                .setTimeline(
                    Timeline.Builder()
                        .addTimelineEntry(
                            TimelineEntry.Builder()
                                .setLayout(createLayout())
                                .build()
                        )
                        .build()
                )
                .build()
        )

    private fun createLayout(): LayoutElement {
        return Column.Builder()
            .addContent(
                Text.Builder()
                    .setText("Steps Today")
                    .setFontStyle(FontStyle.Builder()
                        .setSize(sp(14f))
                        .build())
                    .build()
            )
            .addContent(
                Text.Builder()
                    .setText(getStepCount().toString())
                    .setFontStyle(FontStyle.Builder()
                        .setSize(sp(32f))
                        .setWeight(FONT_WEIGHT_BOLD)
                        .build())
                    .build()
            )
            .build()
    }
}

Phone-Watch Communication on Wear OS

The Wearable Data Layer API handles communication between phone and watch:

class DataLayerManager(private val context: Context) {
    private val dataClient = Wearable.getDataClient(context)
    private val messageClient = Wearable.getMessageClient(context)

    // Send data via DataItem (synced)
    suspend fun sendWorkoutData(workout: WorkoutData) {
        val request = PutDataMapRequest.create("/workout/current").apply {
            dataMap.putString("status", workout.status)
            dataMap.putLong("duration", workout.duration)
            dataMap.putInt("heartRate", workout.heartRate)
            dataMap.putLong("timestamp", System.currentTimeMillis())
        }.asPutDataRequest()
            .setUrgent()

        dataClient.putDataItem(request).await()
    }

    // Send one-off message
    suspend fun sendCommand(command: String) {
        val nodes = Wearable.getNodeClient(context).connectedNodes.await()
        nodes.forEach { node ->
            messageClient.sendMessage(
                node.id, "/command", command.toByteArray()
            ).await()
        }
    }
}

Offline-First Architecture

B

Offline-First Architecture Infographic oth Apple Watch and Wear OS can operate independently from the paired phone. Design your watch app to function offline:

  1. Cache essential data locally on the watch using Core Data (watchOS) or Room (Wear OS)
  2. Queue actions when the phone is not reachable and sync when connectivity returns
  3. Provide meaningful fallbacks when data is stale — show the last known value with a timestamp
  4. Use health sensors directly rather than relying on phone-relayed data

Battery and Performance Considerations

Wearable batteries are small. Every computation counts:

  • Minimise background processing. Use complications and tiles for periodic updates rather than running background tasks.
  • Batch network requests. On Wear OS, use WorkManager for network operations that can be batched.
  • Limit sensor polling frequency. Heart rate every 5 seconds is usually sufficient; every second drains the battery rapidly.
  • Avoid animations unless they serve a clear functional purpose.
  • Profile aggressively. Both Xcode Instruments and Android Studio Profiler support wearable targets.

Launch Checklist

Before shipping your wearable companion app:

  • Test on physical hardware (simulators miss real-world performance and sensor behaviour)
  • Verify offline functionality with phone out of Bluetooth range
  • Test complication/tile updates over 24 hours for memory leaks
  • Validate battery impact over a typical usage day
  • Test with both cellular and Wi-Fi only watch models
  • Ensure your app handles the watch app launching independently of the phone app

Wearable companion apps extend your product’s reach to the most personal device your users own. Build them thoughtfully, and you create engagement that no notification can match.


Building a wearable companion app? Our team at eawesome develops connected experiences across phones, watches, and beyond.