Data Synchronization Between Mobile App and Smartwatch
Sync between phone and watch — not just "send JSON there-back". Bluetooth connection breaks, user goes to shower with watch without phone, app on phone killed by system. Data must arrive in right order, without dupes and losses — while not draining battery on either side.
Two Different Worlds: Wear OS and watchOS
On Wear OS — Wearable Data Layer API. Three channels for different tasks:
| Channel | Size | Delivery Guarantee | When to Use |
|---|---|---|---|
| DataClient | up to 100 KB | Yes (on connect) | Configuration, settings, small data |
| MessageClient | any | No | Real-time commands |
| ChannelClient | unlimited | Yes | Files, media, large datasets |
On watchOS — WatchConnectivity framework. WCSession.transferUserInfo() for background delivery, sendMessage() for interactive exchange (only when both devices available), transferFile() for files.
Mistake everyone makes: use sendMessage() where need transferUserInfo(). sendMessage requires active connection. If watch in airplane mode — data lost. transferUserInfo queues data and delivers on next connect.
Conflict Resolution and Idempotency
Picture: user edits note on phone and watch simultaneously (offline mode). Both devices then sync. Whose data is right?
Without conflict resolution strategy — last wins. Bad for any data except "sensor readings".
Build sync with vector clock or simplified last-write-wins with timestamp. Each change gets updated_at in UTC milliseconds and device_id. On conflict — explicit policy: ask user for personal data, take largest timestamp for telemetry.
Idempotency mandatory: re-delivery of same DataItem must not create duplicate in database. On Wear OS DataClient auto-deduplicates by path (/data/workouts/123) — if data unchanged, onDataChanged not called.
Background Work
On Android watch app receives data via WearableListenerService — launched by system even if app inactive. But Android 13+ and Wear OS 4 added service launch restrictions. WearableListenerService — exception, still works. However if doing heavy ops (Room write, network requests), need coroutine scope tied to service lifecycle.
On iOS WCSession works via application(_:didReceiveUserInfo:) delegate — called even on background delivery. But can't update UI directly — only via DispatchQueue.main.async.
Battery optimization. Each sync — Bluetooth wake-up. Group small updates via batching: instead sending each step separately — aggregate 30 seconds and send one DataItem. On Wear OS use PassiveMonitoringClient for health data collection without constant wake lock.
Practical Example
App for tracking workouts: watch collects heart rate every second, phone stores historical data and shows graphs.
Solution: on watch HealthServicesClient → ExerciseClient.prepareExercise() → aggregate every 5 seconds → MessageClient.sendMessage("/hr-batch", data). On phone WearableListenerService receives batch → parses → WorkManager writes to Room → Flow updates UI.
On connection break watch buffers data locally (Room on watch, up to 10 MB). On restore — ChannelClient transfers accumulated.
Timeframes
Basic two-way sync (config + small data): 1–2 weeks. Full system with offline buffer, conflict resolution, health data: 3–5 weeks. Cost depends on data volume and delivery reliability requirements.







