Трейдер хоче бачити курс біткоїна на головному екрані, не відкриваючи додаток. Але віджет криптовалюти — не просто гарна картинка: він має жити за правилами мобільної ОС. На iOS це WidgetKit + SwiftUI, на Android — Jetpack Glance або класичний AppWidgetProvider. Обидва працюють за принципом snapshot: система запитує актуальний UI в певні моменти, і те, що відобразиться — наша відповідальність. Спираємося на понад 30 реалізованих рішень для криптобірж та 5+ років досвіду мобільної розробки. Оптимізація бюджету проєкту починається з правильного вибору архітектури віджета.
Чому віджет на Home Screen складніший, ніж здається?
На перший погляд віджет здається простим UI-елементом. Але його обмеження щодо оновлення даних, особливо для криптовалют, потребують продуманої архітектури. Розберемо ключові проблеми та їх рішення — на власному досвіді.
Як WidgetKit обмежує оновлення?
WidgetKit не дозволяє віджету робити мережеві запити в реальному часі. Віджет отримує дані через TimelineProvider, який повертає масив TimelineEntry з заздалегідь підготовленими даними та часовими мітками. Система сама вирішує, коли перемалювати віджет.
Для криптовіджета типова стратегія — оновлення кожні 15–30 хвилин через TimelineReloadPolicy.atEnd або .after(date:):
struct CryptoPriceEntry: TimelineEntry { let date: Date let symbol: String let price: Decimal let change24h: Double } struct CryptoPriceProvider: TimelineProvider { func getTimeline(in context: Context, completion: @escaping (Timeline<CryptoPriceEntry>) -> Void) { Task { let price = try? await CryptoAPIClient.shared.fetchPrice(symbol: "BTC") let entry = CryptoPriceEntry(date: .now, symbol: "BTC", price: price?.usd ?? 0, change24h: price?.change24h ?? 0) let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: .now)! let timeline = Timeline(entries: [entry], policy: .after(nextUpdate)) completion(timeline) } } } Детальніше про механізм TimelineProvider
`TimelineProvider` — це протокол, який визначає три методи: `placeholder`, `getSnapshot` та `getTimeline`. `getTimeline` повертає масив записів, кожен з яких містить дату та дані. Система використовує ці записи для відображення віджета у відповідні моменти часу. Після відображення останнього запису віджет запитує новий timeline. Цей цикл дозволяє економити ресурси, але обмежує частоту оновлень.Важливий нюанс: Apple регулює бюджет оновлень. Віджет з високою частотою оновлень на пристроях з низьким зарядом батареї отримує урізаний бюджет — оновлення починають приходити рідше, ніж запрошено. Для трейдингових додатків з вимогою «дані не старше 1 хвилини» віджет не підходить — потрібно чесно пояснити це клієнту до початку розробки. Ми завжди аналізуємо бізнес-вимоги та пропонуємо альтернативи, наприклад, push-сповіщення або Live Activity. Дотримання гайдлайнів допомагає уникнути відхилення в App Store та економить бюджет на доопрацювання.
Передача даних між основним додатком та віджетом — через App Groups + UserDefaults(suiteName:) або FileManager з shared container. @AppStorage всередині віджета працює тільки з App Group suite — без цього віджет не побачить дані, записані основним додатком.
Розміри та адаптація UI
WidgetKit підтримує 4 розміри: .systemSmall, .systemMedium, .systemLarge, .systemExtraLarge (тільки iPad). Для криптовіджета зазвичай робимо small (символ + ціна + зміна) та medium (кілька монет у рядок). SwiftUI у віджеті не підтримує анімації, ScrollView, натискання на довільні області — тільки Link для deep link.
Як Android вирішує ті самі завдання?
Jetpack Glance проти класичного AppWidgetProvider
| Характеристика | Jetpack Glance | AppWidgetProvider |
|---|---|---|
| API | Compose-подібний | RemoteViews |
| Дата появи | Відносно недавно | З самого початку |
| Складність | Нижча (декларативний) | Вища (імперативний) |
| Обмеження | Не всі Compose-модифікатори | Повний контроль |
Jetpack Glance — Compose-подібний API для віджетів, з'явився відносно недавно. Помітно зручніший за класичний RemoteViews, але має обмеження: підтримуються не всі Compose-модифікатори, а частина API працює інакше, ніж у звичайному Compose.
Оновлення даних через GlanceAppWidgetManager.updateIf + WorkManager з періодичним завданням:
class CryptoPriceWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { val prefs = currentState<Preferences>() val price = prefs[priceKey] ?: "—" val change = prefs[changeKey] ?: "0.0" provideContent { Column( modifier = GlanceModifier.fillMaxSize().background(Color.DarkGray).padding(12.dp) ) { Text("BTC", style = TextStyle(color = ColorProvider(Color.White), fontSize = 12.sp)) Text(price, style = TextStyle(color = ColorProvider(Color.White), fontSize = 20.sp)) Text("$change%", style = TextStyle( color = ColorProvider(if (change.startsWith("-")) Color.Red else Color.Green) )) } } } } Мінімальний інтервал оновлення через AppWidgetManager — 30 хвилин (обмеження Android). Для частіших оновлень потрібен WorkManager з PeriodicWorkRequest, але на Android 12+ фонові завдання регулюються Battery Optimizer — в Doze mode інтервали розтягуються.
Порівняння механізмів оновлення iOS та Android
| Параметр | iOS WidgetKit | Android Jetpack Glance |
|---|---|---|
| Мінімальний інтервал | 15-30 хвилин (регулюється системою) | 30 хвилин (WorkManager може більше) |
| Механізм оновлення | TimelineProvider | GlanceAppWidget + WorkManager |
| Обмеження | Battery budget на рівні ОС | Doze mode, Battery Optimizer |
| Рекомендація | Для віджетів, що не потребують real-time | Аналогічно |
Як налаштувати WidgetKit для криптовіджета? (покроково)
- Додати Widget Extension target в Xcode, увімкнути WidgetKit.
- Створити структуру
TimelineEntryз потрібними полями (ціна, зміна, дата). - Реалізувати
TimelineProvider: методиplaceholder,getSnapshot,getTimeline. - У
getTimelineвиконати запит до API, сформувати entry, вказати наступну дату оновлення. - Створити SwiftUI View для віджета, використовуючи
WidgetтаStaticConfiguration. - Налаштувати App Groups для обміну даними з основним додатком.
- Підтримати кілька розмірів через
supportedFamilies.
Типові помилки при розробці криптовіджетів
- Ігнорування бюджетів оновлень на iOS — віджет перестає оновлюватися при низькому заряді.
- Відсутність fallback UI при недоступності мережі — користувач бачить порожній віджет.
- Використання неправильного suite для App Groups — дані не передаються.
- Занадто часті оновлення на Android — конфлікт з Battery Optimizer.
Що входить у роботу
- iOS: WidgetKit extension,
TimelineProvider, SwiftUI-верстка, App Groups для shared data. - Android: Jetpack Glance widget, WorkManager для оновлень.
- Інтеграція з API курсів (CoinGecko, Binance, CoinMarketCap або власний бекенд).
- Підтримка кількох розмірів віджета.
- Deep link з віджета в потрібний екран додатку.
- Тестування поведінки при відсутності мережі та застарілих даних.
- Гарантія на працездатність в App Store та Google Play (дотримання гайдлайнів).
Приклад робочого процесу (наш кейс)
На одному з проєктів — криптогаманець з портфелем — ми реалізували віджет для iOS. Запит клієнта: оновлення кожні 5 хвилин. Довелося використовувати комбінацію WidgetKit + background task для підтримки актуальності. На Android — Glance + WorkManager з політикою 15-хвилинного інтервалу. Підсумок: користувачі в 2 рази частіше повертаються в додаток з віджета.
Терміни
3–5 днів на кожну платформу. Якщо потрібні обидві — 5–8 днів сумарно з урахуванням спільної логіки отримання даних. Вартість розраховується індивідуально — напишіть нам, і ми оцінимо ваш проєкт за 1 робочий день. Якщо вам потрібен віджет для криптовалютного додатку, отримайте консультацію нашої команди.







