Реалізація синхронізації даних умних годин з мобільним додатком
Apple Watch та Wear OS — дві принципово різні екосистеми синхронізації. У Apple — WatchConnectivity framework з жорсткими обмеженнями на передачу. У Google — Data Layer API поверх BLE-стека. Крос-платформенний React Native або Flutter тут не допоможе: синхронізація з годин вимагає нативного коду на Swift/WatchKit та Kotlin/Wearable Data Layer.
Apple Watch: WatchConnectivity
WCSession — єдиний канал між iPhone-додатком та Watch App. Три методи передачи з різною семантикою:
| Метод | Доставка | Розмір | Фон | Сценарій |
|---|---|---|---|---|
sendMessage |
Негайно | < 64 KB | Тільки коли годинник достижимий | Команди в реальному часі |
transferUserInfo |
FIFO-черга | Малий словник | Так, при першій можливості | Налаштування, конфіг |
transferFile |
Фонова передача | До кількох MB | Так | Треки, аудіо, великі дані |
// iPhone → Watch: терміновий команда
class PhoneSessionManager: NSObject, WCSessionDelegate {
func sendWorkoutCommand(_ command: WorkoutCommand) {
guard WCSession.default.isReachable else {
// Годинник недостижимий — ставимо у чергу через transferUserInfo
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
// sendMessage упаде якщо годинник розрядився або вийшов з радіуса
self.queueCommandForLater(command)
}
)
}
}
Важливе обмеження: sendMessage працює тільки якщо WCSession.default.isReachable == true. Це значит годинник не тільки у зоні BLE, але й його додаток активний або у фоні з дозволом. Якщо користувач закрив Watch App — isReachable повернає false навіть коли годинник рядом.
Синхронізація тренувальних даних: Watch → iPhone
Після тренування Watch App збирає дані (ЧСС, каденс, GPS-трек, сегменти) та передає на iPhone:
// Watch App — відправка після завершення тренування
func finishWorkout(_ session: HKWorkoutSession) {
let workoutData = WorkoutSummary(
duration: session.currentActivity.duration,
heartRateSamples: collectedHRSamples,
route: collectedLocations,
)
guard let encoded = try? JSONEncoder().encode(workoutData) else { return }
// Для великих даних (GPS-трек 10k точок) — transferFile
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()])
}
}
На iPhone приймаємо через session(_:didReceiveFile:) або session(_:didReceiveUserInfo:). Файл потрібно перемістити з documentDirectory сесії до виходу з делегатного методу — інакше iOS видалить його.
Wear OS: Wearable Data Layer API
Android-сторона — DataClient, MessageClient, ChannelClient з com.google.android.gms:play-services-wearable.
// Відправка даних з годин на телефон через 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)
}
}
}
}
}
}
// На годинах — запис 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()) // setUrgent() — без затримки синхронізації
.await()
}
DataItem реплікується автоматично — не потрібно стежити за станом з'єднання. Wear OS сама синхронізує коли годинник підключається до телефона.
HealthKit: читання даних тренування на iPhone
Дані тренування, записані Watch App через HealthKit, доступні iPhone-додатку напрямку — без 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
// обробка
}
healthStore.execute(query)
}
Для GPS-трека тренування — HKWorkoutRoute через HKWorkoutRouteQuery. Запитувати потрібно окремо після отримання HKWorkout — маршрут зберігається як зв'язаний об'єкт.
Розробка синхронізації даних умних годин (Apple Watch + Wear OS) з мобільним додатком: 4–6 тижнів. Вартість розраховується індивідуально після аналізу необхідних типів даних та платформ.







