Ви розробили віджет з оновленням раз на 30 хвилин, але користувачі скаржаться на застарілі дані. Курси валют змінюються щохвилини, статус замовлення — у реальному часі. Стандартний updatePeriodMillis з мінімумом 30 хвилин не підходить. Рішення — використовувати WorkManager для періодичних завдань або push-сповіщення від сервера. Наш досвід — 5+ років розробки віджетів для Android, понад 50 проєктів у Google Play. Наприклад, в одному проєкті для торгової платформи ми замінили pull-оновлення на push через FCM, що скоротило затримку з 30 хвилин до 2 секунд. Користувачі одразу помітили різницю. Ми створюємо віджети будь-якої складності: від інформаційних панелей до інтерактивних колекцій з миттєвими оновленнями. Гарантуємо стабільність і відповідність гайдлайнам. Отримайте консультацію щодо вашого завдання.
Як оновлювати віджет швидше 30 хвилин?
Система дозволяє android:updatePeriodMillis не менше 1800000 мс. Для частіших оновлень використовуйте WorkManager з PeriodicWorkRequest (мінімум 15 хвилин, встановлено в API) або AlarmManager. Всередині WorkManager викликайте AppWidgetManager.updateAppWidget(). Для push-оновлень — FirebaseMessagingService.onMessageReceived з прямим викликом updateAppWidget. WorkManager автоматично враховує Doze Mode і Standby Buckets, щоб батарея не страждала. Ось порівняння підходів:
| Спосіб оновлення | Мінімальний інтервал | Рекомендація |
|---|---|---|
| updatePeriodMillis | 30 хвилин | Тільки для даних з низькою частотою змін |
| WorkManager | 15 хвилин | Для регулярних даних (погода, курси) |
| Push (FCM) | Миттєво | Для подієвих сповіщень (статус замовлення, повідомлення) |
Для реалізації push-оновлень додайте серверну інтеграцію, яка надсилає дані на пристрій. Ми використовуємо FCM — push доходять навіть при згорнутому застосунку.
Приклад реалізації оновлення через WorkManager
class WidgetUpdateWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val widgetManager = AppWidgetManager.getInstance(applicationContext)
val widgetIds = widgetManager.getAppWidgetIds(ComponentName(applicationContext, MyWidget::class.java))
val views = buildRemoteViews(applicationContext)
widgetManager.updateAppWidget(widgetIds, views)
return Result.success()
}
}
Чому RemoteViews обмежує інтерактивність?
Android-віджет виконується в процесі лаунчера, тому доступний лише RemoteViews. RemoteViews обмежений базовими View: TextView, ImageView, Button, LinearLayout, RelativeLayout, FrameLayout, GridLayout, ListView, GridView, StackView. Заборонені RecyclerView, ConstraintLayout (до API 31), кастомні View, WebView. Для відображення списків використовуйте ListView або GridView з RemoteViewsFactory. Починаючи з Android 12 (API 31) додані CheckBox, RadioButton, Switch. Важливо: всі елементи тільки через PendingIntent для обробки натискань — assignOnClick — пряме звернення до View не працює. Типова помилка — спроба встановити onClickListener напряму; це неможливо. Використовуйте setOnClickPendingIntent або setPendingIntentTemplate для колекцій.
Glance: декларативний підхід до віджетів
Бібліотека Glance пропонує Compose-подібний синтаксис, приховуючи ручне створення RemoteViews. Ви пишете декларативно: Column, Text, Button. Glance автоматично генерує RemoteViews. Це знижує шанс помилок і прискорює розробку. Однак Glance поки підтримує не всі компоненти: наприклад, LazyColumn немає, використовуйте Column з фіксованою кількістю рядків. Стан керується через GlanceStateDefinition і updateAppWidgetState. Для нового проєкту з minSdkVersion 23+ — це найкращий вибір. Ось мінімальний приклад:
class MyGlanceWidget : GlanceAppWidget() {
@Composable
override fun Content() {
val data = currentState<MyWidgetData>()
Column(modifier = GlanceModifier.fillMaxSize().background(Color.White)) {
Text(text = data.title, style = TextStyle(fontSize = 16.sp))
Button(text = "Оновити", onClick = actionRunCallback<RefreshAction>())
}
}
}
| Характеристика | RemoteViews | Glance |
|---|---|---|
| Синтаксис | Імперативний (XML + Java/Kotlin) | Декларативний (Compose-подібний) |
| Ймовірність помилок | Вища | Нижча |
| Підтримка колекцій | ListView/GridView | Column (фіксована кількість рядків) |
| Мінімальний API | 17 (з урахуванням обмежень) | 23 |
AppWidgetProvider та обробка оновлень
AppWidgetProvider — BroadcastReceiver, який отримує оновлення. У onUpdate() створіть RemoteViews і викличте updateAppWidget(). Типова реалізація:
class MyWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetIds.forEach { widgetId ->
val views = buildRemoteViews(context)
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
Для віджетів з даними з інтернету використовуйте фоновий завантажувач у onReceive або через WorkManager. Ніколи не блокуйте onUpdate — це UI-потік. Обробляйте помилки завантаження: показуйте заглушку (наприклад, TextView з текстом «Помилка завантаження») і плануйте повторну спробу через WorkManager.
Робота з колекціями через RemoteViewsFactory
ListView або GridView у віджеті вимагають RemoteViewsFactory. Фабрика створює RemoteViews для кожного елемента. Натискання на елементи реалізуються через setOnClickFillInIntent і setPendingIntentTemplate. Шаблон — це PendingIntent, який запускатиметься з fill-даними. Приклад:
class WidgetListFactory(private val context: Context) : RemoteViewsService.RemoteViewsFactory {
private var items: List<WidgetItem> = emptyList()
override fun onDataSetChanged() {
items = loadDataFromSharedPrefs(context)
}
override fun getViewAt(position: Int): RemoteViews {
val item = items[position]
return RemoteViews(context.packageName, R.layout.widget_list_item).apply {
setTextViewText(R.id.item_title, item.title)
val fillIntent = Intent().putExtra("item_id", item.id)
setOnClickFillInIntent(R.id.item_container, fillIntent)
}
}
}
Важливо: onDataSetChanged() викликається у фоновому потоці, але сам RemoteViewsFactory може бути закешований. Скидайте кеш за необхідності.
Конфігурація віджету користувачем
AppWidgetConfigure Activity відкривається при додаванні віджету. Користувач обирає параметри (місто, тему, частоту оновлення). Після вибору обов'язково викликати:
setResult(Activity.RESULT_OK, intent.putExtra(EXTRA_APPWIDGET_ID, widgetId))
Без цього віджет не додасться. У Glance конфігурацію запускайте через GlanceAppWidgetManager().startConfigureActivityIntent.
Етапи розробки віджету
- Аналіз вимог: визначення функціональності, частоти оновлень, цільових розмірів (2x2, 4x2, 4x4).
- Проектування макету: вибір між RemoteViews і Glance, створення XML-макету або Compose-інтерфейсу.
- Реалізація AppWidgetProvider: написання логіки оновлень з WorkManager або FCM.
- Інтеграція даних: підключення до API, бази даних (Room) або SharedPreferences.
- Тестування: на емуляторах і реальних пристроях, включаючи Doze Mode.
- Публікація: підготовка метаданих, code signing, розміщення в Google Play.
Що входить у розробку
- Макет RemoteViews з адаптацією під розміри (2x2, 4x2, 4x4).
- AppWidgetProvider з підтримкою оновлень (WorkManager, FCM).
- Конфігураційний екран при необхідності.
- Інтеграція з базою даних (Room) або мережевим API.
- Публікація в Google Play з метаданими, налаштування code signing і provisioning profile.
- Документація та вихідні коди.
Терміни та вартість
Термін розробки одного віджету з конфігурацією та регулярними оновленнями — від 3 до 5 днів. Якщо потрібна колекція з push-оновленнями — до 1 тижня. Вартість розраховується індивідуально: надішліть технічне завдання для оцінки. Отримайте консультацію щодо архітектури — ми допоможемо обрати стек (RemoteViews або Glance) і уникнути типових проблем з продуктивністю. Замовте розробку віджету вже сьогодні — зв'яжіться з нами для початку роботи. Оптимізуйте бюджет — ми пропонуємо гнучкі умови.
Типові помилки при розробці віджетів
- Використання updatePeriodMillis для даних, що потребують частого оновлення. Переходьте на WorkManager або FCM.
- Блокування onUpdate() довгими операціями. Всі фонові завдання виносьте в WorkManager.
- Ігнорування Doze Mode: використовуйте WorkManager з урахуванням Standby Buckets.
- Неправильна обробка конфігурації: не забувайте викликати setResult.
- Відсутність заглушок при помилках завантаження даних.
Ми реалізуємо віджети на Java або Kotlin, включаючи гібридні рішення. Понад 50 успішних проєктів підтверджують наш досвід. Зв'яжіться з нами для оцінки вашого проєкту.







