You built a widget updating every 30 minutes, but users complain about stale data. Currency rates change every minute, order status needs real-time updates. The standard updatePeriodMillis minimum of 30 minutes won't cut it. The solution is to use WorkManager for periodic tasks or push notifications from the server. We have extensive experience developing Android widgets, with over 50 projects in Google Play. For example, in a trading platform project, we replaced pull-based updates with push via FCM, reducing latency from 30 minutes to 2 seconds. Users immediately noticed the difference. Our Android widget development expertise covers RemoteViews, AppWidgetProvider, and Glance for creating polished app widgets. We build widgets of any complexity: from informational dashboards to interactive collections with instant updates. We guarantee stability and compliance with guidelines. Get a consultation for your task.
How to Update Widgets Faster Than 30 Minutes?
The system allows android:updatePeriodMillis no less than 1800000 ms. For more frequent updates, use WorkManager with PeriodicWorkRequest (minimum 15 minutes, set in the API) or AlarmManager. Inside WorkManager, call AppWidgetManager.updateAppWidget(). For push updates — FirebaseMessagingService.onMessageReceived with a direct updateAppWidget call. WorkManager automatically respects Doze Mode and Standby Buckets to preserve battery. Here is a comparison of approaches:
| Method | Minimum interval | Recommendation |
|---|---|---|
| updatePeriodMillis | 30 minutes | Only for low-frequency data |
| WorkManager | 15 minutes | For regular data (weather, rates) |
| Push (FCM) | Instant | For event-driven updates (order status, messages) |
For push updates, add server-side integration that sends data to the device. We use FCM — pushes arrive even when the app is backgrounded.
Example widget update implementation with 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()
}
}
Why RemoteViews Limits Interactivity?
Android widgets run in the launcher process, so only RemoteViews are available. RemoteViews is limited to basic Views: TextView, ImageView, Button, LinearLayout, RelativeLayout, FrameLayout, GridLayout, ListView, GridView, StackView. RecyclerView, ConstraintLayout (before API 31), custom Views, and WebView are forbidden. For displaying lists, use ListView or GridView with RemoteViewsFactory. Starting from Android 12 (API 31), CheckBox, RadioButton, and Switch are added. Importantly, all elements use PendingIntent for click handling — assignOnClick does not work because direct View access is unavailable. A typical mistake is attempting to set onClickListener directly; this is impossible. Use setOnClickPendingIntent or setPendingIntentTemplate for collections.
Glance: A Declarative Approach
The Glance library offers a Compose-like syntax, hiding manual creation of RemoteViews. You write declaratively: Column, Text, Button. Glance automatically generates RemoteViews. This reduces the chance of errors and speeds up development. However, Glance does not yet support all components: for example, LazyColumn is unavailable; use Column with a fixed number of rows. State is managed via GlanceStateDefinition and updateAppWidgetState. For a new project with minSdkVersion 23+, this is the best choice. Here is a minimal example:
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 library - Official GitHub repository
| Feature | RemoteViews | Glance |
|---|---|---|
| Syntax | Imperative (XML + Java/Kotlin) | Declarative (Compose-like) |
| Error likelihood | Higher | Lower |
| Collections support | ListView/GridView | Column (fixed number of rows) |
| Minimum API | 17 (with restrictions) | 23 |
AppWidgetProvider and Update Handling
AppWidgetProvider is a BroadcastReceiver that receives updates. In onUpdate(), create RemoteViews and call updateAppWidget(). A typical implementation:
class MyWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetIds.forEach { widgetId ->
val views = buildRemoteViews(context)
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
For widgets with data from the internet, use a background loader in onReceive or via WorkManager. Never block onUpdate — it is the UI thread. Handle loading errors: show a placeholder (e.g., a TextView with "Error loading") and schedule a retry via WorkManager.
Working with Collections via RemoteViewsFactory
ListView or GridView in a widget require a RemoteViewsFactory. The factory creates RemoteViews for each item. Clicks on items are implemented using setOnClickFillInIntent and setPendingIntentTemplate. The template is a PendingIntent that will be launched with fill data. Example:
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)
}
}
}
Important: onDataSetChanged() is called on a background thread, but the factory itself may be cached. Clear the cache when necessary.
Widget Configuration by User
An AppWidgetConfigure Activity opens when the widget is added. The user selects parameters (city, theme, update frequency). After selection, be sure to call:
setResult(Activity.RESULT_OK, intent.putExtra(EXTRA_APPWIDGET_ID, widgetId))
Without this, the widget will not be added. In Glance, launch configuration via GlanceAppWidgetManager().startConfigureActivityIntent.
Development Process
- Requirements analysis: define functionality, update frequency, target sizes (2x2, 4x2, 4x4).
- Layout design: choose between RemoteViews and Glance, create XML layout or Compose interface.
- Implementation: write AppWidgetProvider logic with WorkManager or FCM.
- Data integration: connect to API, database (Room) or SharedPreferences.
- Testing: on emulators and real devices, including Doze Mode.
- Publication: prepare metadata, code signing, submit to Google Play.
What's Included
- RemoteViews layout adapted to sizes (2x2, 4x2, 4x4).
- AppWidgetProvider with update support (WorkManager, FCM).
- Configuration screen if needed.
- Integration with database (Room) or network API.
- Publication in Google Play with metadata, code signing and provisioning profile setup.
- Documentation and source code.
Timelines and Pricing
Development of a single widget with configuration and regular updates takes 3 to 5 days. If a collection with push updates is required, up to 1 week. Typical cost: simple widget $800–$1500, complex widget with push $2000–$4000. Clients save 20% on development time using our optimized approach. Pricing is calculated individually: send a technical specification for a quote. Get a consultation on architecture — we'll help you choose the stack (RemoteViews or Glance) and avoid typical performance issues. Order widget development today — contact us to start. Optimize your budget — we offer flexible terms.
Common Mistakes in Widget Development
- Using
updatePeriodMillisfor data that requires frequent updates. Switch to WorkManager or FCM. - Blocking
onUpdate()with long operations. Move all background tasks to WorkManager. - Ignoring Doze Mode: use WorkManager with respect to Standby Buckets.
- Incorrect configuration handling: don't forget to call
setResult. - Missing placeholders for data loading errors.
We implement widgets in Java or Kotlin, including hybrid solutions. Over 50 successful projects confirm our expertise — with an average rating of 4.8 stars and over 100,000 active installations. Contact us for a project evaluation.







