Разработка виджетов для 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 недели. Стоимость рассчитывается индивидуально.







