Трейдер хочет видеть курс биткоина на главном экране, не открывая приложение. Но виджет криптовалюты — не просто красивая картинка: он должен жить по правилам мобильной ОС. На 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 рабочий день. Если вам нужен виджет для криптовалютного приложения, получите консультацию нашей команды.







