Розробка віджетів для Android (App Widgets)
Android App Widgets — компоненти, що розміщуються на домашньому екрані та оновлюються через AppWidgetProvider (підклас BroadcastReceiver). На відміну від iOS WidgetKit, Android-віджети можуть реагувати на дотики інтерактивно та оновлюватися без обмежень бюджету iOS — але з обмеженнями RemoteViews.
Архітектура: RemoteViews як головне обмеження
Android-віджет рендерується в процесі launcher'а, не додатка. Тому використовувати звичайні View не можна — лише 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): RemoteViews підтримує CheckBox, RadioButton, Switch — інтерактивні елементи. До 12 — лише PendingIntent на дотик.
AppWidgetProvider та оновлення
class MyWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetIds.forEach { widgetId ->
val views = buildRemoteViews(context)
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
onUpdate викликається за розписанням з android:updatePeriodMillis в appwidget-provider XML. Мінімальне значення — 1800000ms (30 хвилин). Для більш частих оновлень потрібен WorkManager або AlarmManager з явним вызовом updateAppWidget.
WorkManager з PeriodicWorkRequest (мінімум 15 хвилин) + appWidgetManager.updateAppWidget — стандартний підхід для віджетів з регулярними даними (курси, погода, розписання).
Для push-driven оновлень: FirebaseMessagingService.onMessageReceived → appWidgetManager.updateAppWidget — віджет оновлюється миттєво при отримання push.
Колекції: RemoteViewsFactory
ListView у віджеті з даними з додатка:
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)
}
}
}
setOnClickFillInIntent + setPendingIntentTemplate на ListView — патерн для обробки дотиків елементів. Без setPendingIntentTemplate дотики не працюють.
Glance — Jetpack Compose для віджетів
Починаючи з Glance 1.0 (stable): віджети на Compose-подібному синтаксисі, без RemoteViews вручну.
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>())
}
}
}
Glance обмежена підмножиною Compose — не всі модифікатори та компоненти доступні. GlanceModifier відрізняється від стандартного Modifier. Стан — через GlanceStateDefinition та updateAppWidgetState. Найкращий вибір для нових проектів на API 23+.
Конфігурація віджета користувачем
AppWidgetConfigure Activity — відкривається при додаванні віджета. Користувач вибирає параметри (місто, тему, дані для відображення). Після вибору: setResult(Activity.RESULT_OK, intent.putExtra(EXTRA_APPWIDGET_ID, widgetId)) — обов'язково, інакше віджет не додасться.
У Glance: GlanceAppWidgetManager().startConfigureActivityIntent — запуск конфігурації програмно.
Термін: 3-5 днів для одного віджета з даними та конфігурацією. З колекцією та push-оновленнями — до 1 тижня. Вартість розраховується індивідуально.







