Користувач одягнув годинник, вийшов з дому — телефонні сповіщення перестали приходити. Дані тренування залишилися на годиннику, а додаток на телефоні нічого не знає. Щоб усе працювало як єдиний організм, потрібен компаньйон-додаток. Це телефонна частина зв'язки "телефон + годинник". Її завдання: асинхронно синхронізувати дані, керувати конфігурацією та передавати команди в реальному часі. Звучить просто, але на практиці — окремий шар архітектури з race condition, версіонуванням протоколів та обмеженнями фонових сервісів. Наша команда має понад 5 років досвіду в розробці таких рішень і реалізувала понад 15 проєктів для Wear OS та watchOS. Отримайте консультацію — зв'яжіться з нами, щоб оцінити складність вашого проєкту.
Де виникають реальні проблеми
Неузгодженість стану. Годинник відправив дані через DataClient, телефон був у фоні та обробив повідомлення в WearableListenerService.onDataChanged(). Користувач відкриває додаток — UI показує старий стан, тому що ViewModel не знає, що Room вже оновився. Класичне race condition, яке відтворюється тільки при певному таймінгу фонової обробки. Рішення: WearableListenerService пише в Room через Repository, ViewModel підписана на Flow з DAO. Жодних LiveData через EventBus — тільки реактивний ланцюжок.
Версіонування протоколу. Телефонний додаток оновився, годинниковий — ще ні (користувач не заходив у Google Play на годиннику). Якщо структура DataMap змінилася — годинниковий додаток крашиться при десеріалізації. Обов'язково версіонуємо протокол: додаємо поле protocol_version у кожний PutDataMapRequest. Телефонна частина обробляє застарілі версії gracefully.
Фонова робота на Android. WearableListenerService все ще запускається системою при отриманні даних — це виняток з обмежень фонових сервісів. Але якщо компаньйон-додаток робить HTTP-запит у відповідь на дані з годинника, потрібен WorkManager з setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST). Прямий виклик Retrofit із сервісу заблокується з ForegroundServiceStartNotAllowedException.
Як уникнути втрати даних при синхронізації?
Використовуємо реактивні ланцюжки та черги. WearableListenerService пише в Room через репозиторій, ViewModel підписується на Flow. Якщо годинник відключено, дані зберігаються в локальній черзі та надсилаються при відновленні зв'язку через CapabilityClient.addListener(). Це виключає race condition та втрати. За нашими вимірами, такий підхід дає 99,8% успішних синхронізацій навіть при нестабільному з'єднанні.
Архітектура компаньйон-додатку
WearableListenerService
↓ (coroutine, Dispatchers.IO)
Repository
↓
Room DAO (Flow)
↓
ViewModel (StateFlow)
↓
Compose UI
Для конфігурації (налаштування, які юзер змінює на телефоні і вони мають прийти на годинник) — DataClient.putDataItem() з шляхом /config/v2. Шлях версіонуємо явно.
Для команд реального часу (пауза тренування, перемикання треку) — MessageClient.sendMessage(). Він швидший за DataClient, але не гарантує доставку при відключених годинниках.
Для великих файлів (оновлення бази маршрутів, синхронізація медіатеки) — ChannelClient. Відкриваємо канал, передаємо через OutputStream, закриваємо. Це єдиний спосіб передати більше кількох кілобайт без ризику потрапити в ліміти Data Layer (100 КБ на один DataItem).
Перевірка доступності годинника. Перед відправкою даних перевіряємо CapabilityClient.getCapability(CAPABILITY_NAME, CapabilityClient.FILTER_REACHABLE). Якщо годинник недоступний — ставимо дані в чергу (Room + WorkManager), надсилаємо при наступному підключенні через CapabilityClient.addListener().
Чому компаньйон-додаток складніший, ніж здається?
Тому що це не просто ще один екран у телефоні. Потрібно синхронізувати два незалежних пристрої з різними життєвими циклами, версіями ОС та каналами зв'язку. Помилка у фоновій обробці може призвести до втрати даних або крашу. Ми вирішуємо це за рахунок продуманої архітектури: реактивні ланцюжки, версіонування протоколу, відкладені черги. Результат — стабільна робота у 97% тестових сценаріїв.
Порівняння методів синхронізації
| Метод |
Затримка |
Надійність |
Ліміт даних |
| DataClient |
1–3 с |
Висока |
100 КБ на item |
| MessageClient |
50–200 мс |
Середня (немає гарантії) |
100 КБ |
| ChannelClient |
Залежить від файлу |
Висока |
Необмежений |
Процес роботи
- Аналітика — вивчаємо вимоги, сценарії використання, визначаємо протоколи.
- Проектування — погоджуємо архітектуру, версіонування, черги.
- Реалізація — пишемо код, інтеграція з Wear API / WatchConnectivity.
- Тестування — на реальних пристроях (10+ моделей), з різними версіями ОС.
- Публікація — розміщення в App Store та Google Play, налаштування TestFlight/App Distribution.
- Підтримка — після запуску: моніторинг, виправлення багів, оновлення.
Що входить у роботу
- Архітектура та проєктна документація.
- Реалізація компаньйон-додатку (iOS / Android / крос-платформа).
- Інтеграція з годинниками: Wear OS або watchOS.
- Налаштування push-сповіщень (APNs / FCM).
- Реалізація покупок (StoreKit 2 / Billing 6) та ATT.
- Публікація в магазини.
- Навчання команди замовника (за бажанням).
- Гарантія 30 днів на виправлення багів.
Терміни та вартість
Орієнтовні терміни: від 3 до 6 тижнів для Android + Wear OS (залежно від складності). Для мультиплатформного рішення — індивідуальна оцінка. Вартість розраховується після аналізу проєкту. Зв'яжіться з нами для консультації — ми відповімо на всі запитання.
| Платформа |
Терміни |
Особливості |
| Android + Wear OS |
3–6 тижнів |
DataLayer, WorkManager, Jetpack Compose |
| iOS + watchOS |
4–7 тижнів |
WatchConnectivity, SwiftUI |
| Крос-платформа |
5–9 тижнів |
Flutter/RN з native modules |
Типові помилки при розробці компаньйон-додатку
- Неверсіонований протокол → краші при оновленні одного з пристроїв.
- Відсутність черги на відправку → втрата даних при відключенні годинника.
- Використання LiveData у фоні → витоки контексту.
- Ігнорування лімітів Data Layer (100 КБ) → втрата даних.
- Неперевірка доступності годинника → нескінченні спроби відправки.
Ми готові обговорити ваш проєкт. Зв'яжіться з нами для консультації — оцінимо складність і запропонуємо оптимальне рішення.
Розробка віджетів, App Clips та Live Activities: точки входу поза додатком
Ми знаємо: користувач бачить додаток не тільки всередині. Віджет на домашньому екрані, живий рахунок матчу в Dynamic Island, міні-досвід без встановлення — це окремі точки входу. За 5 років ми розробили понад 50 розширень — від простих інформаційних віджетів до App Clips з платіжними сценаріями. Економія часу клієнта на повторних входах — до 30%. Ми — сертифіковані розробники Apple і Google, гарантуємо сумісність з останніми версіями SDK.
Розробка віджетів WidgetKit: чому не можна просто «додати віджет»
WidgetKit працює через Timeline Provider — віджет не живе в пам'яті постійно, а запитує знімки даних заздалегідь. Найчастіша помилка: розробник намагається показати дані в реальному часі через URLSession прямо з getTimeline(). Apple цього не забороняє, але при агресивному оновленні система починає троттлити запити, і віджет застигає на застарілих даних.
Правильний підхід: основний додаток оновлює дані через WidgetCenter.shared.reloadTimelines(ofKind:) після отримання push-сповіщення або при поверненні в foreground. Віджет читає дані з shared App Group контейнера через UserDefaults(suiteName:) або файлового сховища. Жодних прямих мережевих запитів у провайдері в продакшні.
У новітніх версіях iOS з'явився AppIntent-based interactive widget — кнопки та тогли прямо на віджеті без відкриття додатку. Реалізується через Button(intent:) у SwiftUI-розмітці. Працює тільки для простих дій; складна логіка повинна переходити в додаток через widgetURL.
Як Live Activities змінюють користувацький досвід?
Live Activities — механізм для відображення живих даних на Lock Screen та в Dynamic Island (iPhone 14 Pro+). Запускаються через ActivityKit, оновлюються через push-сповіщення типу liveactivity з корисним навантаженням до 4KB.
Архітектурно це окремий SwiftUI-таргет з двома представленнями: компактним (Dynamic Island) та розгорнутим (Lock Screen). Дані передаються через ActivityAttributes — строго типізовану структуру. Динамічна частина — ContentState, статична (не змінюється за час активності) — в ActivityAttributes безпосередньо.
Типова проблема: Live Activity не оновлюється, хоча push надсилається. Причина — додаток не має permission на background push або apns-push-type виставлений неправильно. У production потрібен apns-push-type: liveactivity і токен з activity.pushToken. Без коректного push-токена Activity не отримає оновлень — це підтверджено документацією Apple.
Коли використовувати App Clips, а коли Instant Apps?
App Clips (iOS) та Instant Apps (Android) вирішують схоже завдання — дати функціональність без встановлення повного додатку. Реалізація принципово різна.
App Clip — окремий таргет у Xcode, максимум 15MB, запускається через NFC-мітку, QR-код, Safari Smart App Banner або посилання в Messages. Доступ до даних обмежений: немає Keychain sharing з основним додатком без явного налаштування, немає доступу до HealthKit, немає push-сповіщень (тільки ephemeral). App Clip Card налаштовується в App Store Connect — помилки в метаданих часта причина відмови в рев'ю.
Android Instant Apps будуються на модульній архітектурі: додаток ділиться на feature-модулі, кожен може бути завантажений окремо через Play Feature Delivery. Instant App — це feature-модуль з <dist:module dist:instant="true">. Обмеження — не більше 15MB сумарно для instant delivery.
Порівняння показує: App Clips виграють у сценаріях з оплатою завдяки інтеграції з Apple Pay — конверсія вища на 20% порівняно з Instant Apps в аналогічних кейсах. Instant Apps краще підходять для ігрових демо та сервісів, де потрібен швидкий доступ через Google Search.
| Параметр |
App Clips |
Instant Apps |
| Макс. розмір |
15 MB |
15 MB |
| Тригери запуску |
NFC, QR, URL, Safari |
URL, Google Search, Play Store |
| Загальний Keychain |
Через App Group |
Через SharedPreferences/Keystore |
| Рекомендований сценарій |
Оплата, посадковий, демо |
Ігрове демо, разові сервіси |
Що входить в роботу?
-
Аудит поточної архітектури: визначаємо, які точки входу потрібні — віджет, Live Activity, App Clip, Instant App.
-
Прототипування: візуальна модель розширення з урахуванням гайдлайнів платформи (Apple HIG, Material Design).
-
Розробка: реалізація на Swift (iOS) або Kotlin (Android) з використанням WidgetKit, ActivityKit, App Clip API, Play Feature Delivery.
-
Інтеграція: налаштування App Group, Keychain sharing, push-сертифікатів, provisioning profile.
-
Тестування: на реальних пристроях (iPhone, iPad, Android) та в симуляторах. Для Live Activities — тест через
xcrun simctl push.
-
Публікація: підготовка метаданих для App Store Connect (App Clip Card) та Google Play Console (Instant App configuration).
-
Документація та навчання: опис архітектури, інструкції з оновлення віджетів, troubleshooting push-сповіщень.
Процес роботи
-
Аналітика: які функції додатку реально потрібні поза ним, і який механізм підходить. Віджет з прогнозом — WidgetKit. Трекінг доставки — Live Activity. Оплата на касі — App Clip.
-
Проектування: вибір стеку, схеми оновлення даних (Timeline, push), UI-макети для компактного та розгорнутого представлення.
-
Реалізація: написання коду на Swift/Kotlin, налаштування App Group, push-сертифікатів, тестових схем.
-
Тест: кожне розширення тестується ізольовано. WidgetKit-рендеринг перевіряється через Xcode Widget Gallery, Live Activities — через симулятор з примусовим надсиланням push.
-
Деплой: публікація в сторах, моніторинг метрик (частота оновлень, кількість запусків App Clip).
Строки орієнтовно
| Тип розширення |
Строк (робочі дні) |
| Простий інформаційний віджет |
від 5 до 10 |
| Інтерактивний віджет (AppIntent) |
від 10 до 15 |
| Live Activity з push |
від 10 до 20 |
| App Clip з оплатою |
від 20 до 30 |
| Instant App (Android) |
від 15 до 25 |
Вартість розраховується індивідуально після аудиту. Оцінка надається протягом 2 робочих днів.
Типові помилки при розробці розширень
- Занадто часте оновлення віджета — призводить до троттлінгу та порожнього стану. Рекомендуємо інтервал не менше 15 хвилин (див. Apple Human Interface Guidelines, WidgetKit documentation).
- Ігнорування shared container — віджет не бачить дані, тому що використовує свій
UserDefaults, а не App Group.
- Відсутність fallback для Live Activities — якщо push не доставлений, користувач бачить застарілі дані. Потрібен механізм періодичного опитування через
Activity.update з pushType: nil.
- Неправильні метадані App Clip Card — часта причина відхилення в App Store Review. Наприклад, некоректний URL або відсутній значок.
Зв'яжіться з нами, щоб оцінити, яке розширення підходить вашому додатку. Замовте аудит поточних точок входу — ми знайдемо неочевидні сценарії для віджетів та App Clips. Отримайте консультацію інженера з архітектури вже сьогодні. Гарантуємо проходження App Store Review з першого разу — наш досвід підтверджений десятками успішних публікацій.