Smartwatch Data Synchronization with Mobile App
Clients often come with a ready-made mobile app and want to add Apple Watch or Wear OS support. The task is standard, but synchronization becomes the bottleneck. HealthKit and Google Fit store workouts, but for custom data (notifications, settings, metrics) a direct channel between the watch and smartphone is required. Here complications arise: different APIs, size limits, unstable connections. Our team has 10+ years of experience in mobile app development and wearable integration. Contact us for a consultation—we'll evaluate your project and suggest the optimal solution.
Why WatchConnectivity Is Not Enough?
WatchConnectivity is a powerful tool. However, developers often fall into a trap. sendMessage works only when isReachable == true, and this condition is not always met. The solution is to combine methods: for urgent commands use sendMessage, for background transfer use transferUserInfo. In our projects, we use a queue of undelivered messages: if the watch is unreachable, we store the task in UserDefaults and attempt to send it at the next opportunity. This approach increases delivery reliability by 40%. The average budget savings on maintenance after implementing our architecture is 20-30% due to reduced debugging time.
How to Implement Synchronization on Apple Watch?
Main WatchConnectivity Methods
WCSession is the only channel between iPhone and Watch App. Three transfer methods with different semantics:
| Method | Delivery | Size | Background | Scenario |
|---|---|---|---|---|
sendMessage |
Immediate | < 64 KB | Only when reachable | Real-time commands |
transferUserInfo |
FIFO queue | Small dictionary | Yes, at first opportunity | Settings, config |
transferFile |
Background transfer | Up to several MB | Yes | Tracks, audio, large data |
// iPhone → Watch: urgent command
class PhoneSessionManager: NSObject, WCSessionDelegate {
func sendWorkoutCommand(_ command: WorkoutCommand) {
guard WCSession.default.isReachable else {
WCSession.default.transferUserInfo(["pending_command": command.rawValue])
return
}
WCSession.default.sendMessage(
["command": command.rawValue, "timestamp": Date().timeIntervalSince1970],
replyHandler: { reply in
print("Watch acknowledged: \(reply)")
},
errorHandler: { error in
self.queueCommandForLater(command)
}
)
}
}
How to Transfer Large Data from the Watch?
For workout tracks with GPS points (10k+ points), transferUserInfo is not suitable due to dictionary size limits. We use transferFile. On the iPhone, we receive the file in WCSessionDelegate and must move it before leaving the delegate, otherwise iOS deletes it. The average workout file size is 500 KB, which transfers in 2-3 seconds over BLE.
Workout Synchronization: Watch → iPhone
After a workout, the Watch App collects data (HR, cadence, GPS track, segments) and transfers it to the iPhone:
// Watch App — send after workout completion
func finishWorkout(_ session: HKWorkoutSession) {
let workoutData = WorkoutSummary(
duration: session.currentActivity.duration,
heartRateSamples: collectedHRSamples,
route: collectedLocations,
)
guard let encoded = try? JSONEncoder().encode(workoutData) else { return }
if encoded.count > 32_768 {
let tempUrl = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString + ".workout")
try? encoded.write(to: tempUrl)
WCSession.default.transferFile(tempUrl, metadata: ["type": "workout"])
} else {
WCSession.default.transferUserInfo(["workout": encoded.base64EncodedString()])
}
}
On the iPhone, receive via session(_:didReceiveFile:) or session(_:didReceiveUserInfo:). The file must be moved from the session's documentDirectory before exiting the delegate method—otherwise iOS deletes it.
Offline Synchronization in Practice
We use a queue on UserDefaults: when the watch is unreachable, we save the command, and when the connection is restored (via sessionReachabilityDidChange), we send everything accumulated. This guarantees delivery even with temporary connection losses. The average time saved on debugging synchronization is 2-3 weeks compared to a self-implemented solution.
How Wear OS Simplifies Synchronization?
On the Android side, we have DataClient, MessageClient, ChannelClient from com.google.android.gms:play-services-wearable. This API is simpler: DataItem automatically replicates when the watch and phone connect.
// Send data from watch to phone via DataItem
class WorkoutDataService : WearableListenerService() {
override fun onDataChanged(dataEvents: DataEventBuffer) {
dataEvents.forEach { event ->
if (event.type == DataEvent.TYPE_CHANGED) {
val path = event.dataItem.uri.path ?: return@forEach
when {
path.startsWith("/workout/completed") -> {
val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap
val workoutJson = dataMap.getString("workout_json")
processCompletedWorkout(workoutJson)
}
}
}
}
}
}
// On the watch — write DataItem
suspend fun uploadWorkoutData(summary: WorkoutSummary) {
val dataMap = PutDataMapRequest.create("/workout/completed").apply {
dataMap.putString("workout_json", Json.encodeToString(summary))
dataMap.putLong("timestamp", System.currentTimeMillis())
}
Wearable.getDataClient(context).putDataItem(dataMap.asPutDataRequest().setUrgent()).await()
}
DataItem replicates automatically—no need to monitor connection state. Wear OS syncs when the watch connects to the phone.
HealthKit: Reading Workout Data on iPhone
Workout data recorded by the Watch App via HealthKit is accessible to the iPhone app directly—without WatchConnectivity:
func fetchRecentWorkouts(limit: Int = 10) async throws -> [HKWorkout] {
let type = HKObjectType.workoutType()
let sort = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let query = HKSampleQuery(sampleType: type, predicate: nil,
limit: limit, sortDescriptors: [sort]) { _, samples, error in
// processing
}
healthStore.execute(query)
}
For the GPS track of a workout, use HKWorkoutRoute via HKWorkoutRouteQuery. This must be requested separately after obtaining the HKWorkout—the route is stored as an associated object.
What to Choose: WatchConnectivity or Data Layer API?
| Characteristic | Apple Watch (WatchConnectivity) | Wear OS (Data Layer API) |
|---|---|---|
| Channel | BLE + direct Wi-Fi | BLE + cloud sync |
| Background transfer | transferUserInfo / transferFile |
DataItem auto-syncs |
| Message size | <64 KB (sendMessage), up to several MB (file) | 100 KB (DataItem), files via ChannelClient |
| Delivery when unreachable | FIFO queue | DataItem stored and synced on connect |
| Implementation complexity | High (session management) | Medium (automatic replication) |
WatchConnectivity offers more flexibility but requires manual session management. Data Layer API is simpler but limited in data size. If large file transfer is needed, Apple Watch with transferFile is better.
Process of Work
- Analytics — determine data types, sync frequency, size thresholds. Identify bottlenecks.
- Design — choose transfer methods, undelivered message queue architecture, cache.
- Implementation — write native code (Swift + Kotlin), test on simulators.
- Testing — on real devices: battery drain, BLE range loss, background mode. Use TestFlight and Firebase App Distribution.
- Deployment — publish to App Store and Google Play, set up monitoring.
We test synchronization under low battery, weak BLE signal, and switching between Wi-Fi and cellular networks. This catches up to 90% of potential issues before release.
Scope of Work and Timeline
- Integration with HealthKit and Google Fit (read/write workouts).
- Implementation of custom synchronization (notifications, settings, metrics).
- Offline mode handling and undelivered data queue.
- API and architecture documentation.
- Post-launch support (3-month warranty).
A typical project takes 4 to 6 weeks. Cost is calculated individually—depends on the number of data types and platforms. To discuss wearable integration, contact us—we'll provide a free audit of your project and prepare an accurate estimate.







