Клієнти часто приходять з готовим мобільним додатком і хочуть додати підтримку Apple Watch або Wear OS. Задача стандартна, але синхронізація стає вузьким місцем. HealthKit та Google Fit зберігають тренування, але для передачі кастомних даних (сповіщення, налаштування, метрики) потрібен прямий канал між годинником і смартфоном. Тут виникають складнощі: різні API, обмеження за розміром, нестабільне з'єднання. Наша команда має 10+ років досвіду в розробці мобільних додатків та інтеграції з носимими пристроями. Отримайте консультацію з інтеграції — ми оцінимо ваш проєкт та запропонуємо оптимальне рішення.
Чому WatchConnectivity недостатньо?
WatchConnectivity — потужний інструмент. Однак розробники часто потрапляють у пастку. sendMessage працює тільки коли isReachable == true, а ця умова виконується не завжди. Рішення — комбінувати методи: для термінових команд sendMessage, для фонової передачі — transferUserInfo. Ми у своїх проєктах використовуємо чергу недоставлених повідомлень: якщо годинник недосяжний, ставимо задачу в UserDefaults і намагаємося відправити при наступній можливості. Такий підхід підвищує надійність доставки на 40%. Середня економія бюджету на підтримку після впровадження нашої архітектури становить 20-30% за рахунок зниження часу на налагодження.
Як реалізувати синхронізацію на 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 {
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)
}
)
}
}
Як передати великі дані з годинника?
Для треків тренувань з GPS-точками (10k+ точок) transferUserInfo не підходить через обмеження словника. Використовуємо transferFile. На iPhone приймаємо файл у WCSessionDelegate і обов'язково переміщаємо його до виходу з делегату, інакше iOS видалить. Середній розмір файлу тренування — 500 KB, що передається за 2-3 секунди по BLE.
Синхронізація тренувань: 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 }
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 видалить його.
Офлайн-синхронізація на практиці
Використовуємо чергу на UserDefaults: при недосяжності годинника зберігаємо команду, а при відновленні з'єднання (через sessionReachabilityDidChange) відправляємо все накопичене. Це гарантує доставку навіть при тимчасових втратах зв'язку. Середня економія часу на налагодження синхронізації становить 2-3 тижні порівняно з самостійною реалізацією.
Як Wear OS спрощує синхронізацію?
Android-сторона — DataClient, MessageClient, ChannelClient з com.google.android.gms:play-services-wearable. Цей API простіший: DataItem автоматично реплікується, коли годинник і телефон з'єднуються.
// Відправлення даних з годинника на телефон через 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()).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 — маршрут зберігається як пов'язаний об'єкт.
Що обрати: WatchConnectivity чи Data Layer API?
| Характеристика | Apple Watch (WatchConnectivity) | Wear OS (Data Layer API) |
|---|---|---|
| Канал | BLE + прямий Wi-Fi | BLE + хмарна синхронізація |
| Фонова передача | transferUserInfo / transferFile |
DataItem автоматично синхронізується |
| Розмір повідомлення | <64 KB (sendMessage), до кількох MB (file) | 100 KB (DataItem), файли через ChannelClient |
| Доставка при недосяжності | FIFO-черга | DataItem зберігається і синхронізується при підключенні |
| Складність реалізації | Висока (керування сесією) | Середня (автоматична реплікація) |
WatchConnectivity дає більше гнучкості, але вимагає ручного керування сесією. Data Layer API простіший, але обмежений за розміром даних. Якщо потрібна передача великих файлів, краще використовувати Apple Watch з transferFile.
Процес роботи
- Аналітика — визначаємо типи даних, частоту синхронізації, пороги за розміром. На цьому етапі виявляємо вузькі місця.
- Проєктування — обираємо методи передачі, архітектуру черги недоставлених повідомлень, кеш.
- Реалізація — пишемо нативний код (Swift + Kotlin), тестуємо на симуляторах.
- Тестування — на реальних пристроях: розряд батареї, вихід із зони BLE, фоновий режим. Використовуємо TestFlight та Firebase App Distribution.
- Деплой — викладаємо в App Store та Google Play, налаштовуємо моніторинг.
Ми перевіряємо синхронізацію при низькому заряді, слабкому сигналі BLE та при перемиканні між Wi-Fi та стільниковою мережею. Це дозволяє виявити до 90% потенційних проблем до релізу.
Обсяг робіт та терміни
- Інтеграція з HealthKit та Google Fit (читання/запис тренувань).
- Реалізація кастомної синхронізації (сповіщення, налаштування, метрики).
- Обробка офлайн-режиму та черги недоставлених даних.
- Документація з API та архітектури.
- Підтримка після запуску (гарантія 3 місяці).
Типовий проєкт триває від 4 до 6 тижнів. Вартість розраховується індивідуально — залежить від кількості типів даних та платформ. Щоб обговорити інтеграцію з носимими пристроями, зв'яжіться з нами — ми проведемо безкоштовний аудит вашого проєкту та підготуємо точний кошторис.







