Glance Appwidget Updates And Memory
Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)
- Skill
- Tool integration
- Software development
- Apache-2.0
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a comprehensive workflow for managing Android AppWidget state and memory, specifically addressing the strict bitmap memory limits introduced in Android 17 (API 37). It guides developers through configuring Jetpack Glance and classic AppWidgetProvider implementations to ensure stability, proper image downsampling, and efficient background synchronization. The skill covers memory-efficient bitmap handling, FileProvider integration for content URIs, and robust state management using DataStore and WorkManager to prevent common crashes and UI staleness.
What it does
- Configures Android 17 (API 37) SDK levels and manifest requirements for widgets.
- Implements bitmap downsampling to stay within strict RemoteViews memory limits.
- Provides patterns for deduplicating shared bitmaps across responsive size buckets.
- Guides the use of FileProvider for sharing content URIs securely with launchers.
- Ensures widget UI refreshes after DataStore state mutations.
- Fixes common crashes like NoSuchMethodException and ForegroundServiceStartNotAllowedException.
When to use it
- When targeting Android 17 (API 37) and encountering RemoteViews memory limit exceptions.
- When developing or maintaining Jetpack Glance widgets that display images.
- When migrating classic AppWidgetProvider code to handle background updates safely.
- When debugging frozen or stale widget UI after state updates.
- When implementing responsive widget layouts that require efficient memory management.
Advantages
- Prevents IllegalArgumentException crashes related to widget bitmap memory limits.
- Improves widget performance by optimizing image decoding and deduplication.
- Ensures reliable background updates without violating Android service restrictions.
- Provides clear patterns for modern Glance and legacy AppWidget interoperability.
Usage examples
- “How do I fix the RemoteViews bitmap memory limit exception on Android 17?”
- The agent provides steps to calculate the memory cap using WindowMetrics and implement downsampling helpers to ensure images fit within the system-enforced budget.
- “My Glance widget UI is not updating after I change DataStore preferences.”
- The agent explains how to trigger an explicit update using GlanceAppWidget.update() or updateAll() after calling updateAppWidgetState.
- “How can I safely update images in a classic AppWidgetProvider without causing crashes?”
- The agent guides you to use AppWidgetManager.updateAppWidget instead of partiallyUpdateAppWidget and shows how to schedule background work correctly.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/android/glance-appwidget-updates-and-memory/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip glance-appwidget-updates-and-memory.zip -d .agents/skills
Documentation by Google LLC
This skill provides guidance for managing state updates and keeping Android
AppWidgets that use Jetpack Glance (androidx.glance:glance-appwidget) or
RemoteViews within the Android 17 widget bitmap memory limits. For widget UI
layout templates, suggest the glance-canonical-layouts skill.
When configuring a Glance widget, see Create an app widget with Glance.
When targeting Android 17 (
targetSdk = 37), see Android 17 widget memory limits.When maintaining a classic
AppWidgetProvider, see Create an app widget.
Prerequisites
Kotlin Android project (
minSdk >= 26) withcompileSdk = 37(targetSdk = 37when targeting Android 17) inbuild.gradle.kts.Jetpack Glance (
1.2.0, minimum1.1.1+) and the Compose compiler Gradle plugin enabled on the module.
Critical mandates
Preserve existing package name and toolchain versions : Inspect
build.gradle.kts,gradle/libs.versions.toml, and Kotlin files first. Keep existingnamespace,applicationId,package,targetSdk(unless migrating to Android 17), Android Gradle Plugin (AGP), and Kotlin versions.Downsample images to widget dimensions and estimate the memory cap : Android
system_servercomputes theRemoteViewsbitmap memory cap (6 * width * heightbytes, or1.5 * 4 * width * height) once at boot from the real size of the default display and does not expose this cap in a public API. Because unfolding a foldable or switching display resolution toQHD+after boot can make active window metrics report a larger value than the boot-time cap, always downsample each image first to the widget's target dimensions (maxWidthDp x maxHeightDpinRGB_565orALPHA_8), and enforce the per-image byte ceiling (calculateWidgetMemoryCapBytes(context) / distinctImageCount) estimated as6 * width * heightfromWindowManager.currentWindowMetrics.boundson API 30+ (falling back toresources.displayMetricson API 26-29 or ifWindowManageris unavailable).Decode shared bitmaps once outside provideContent : Both
SizeMode.Exact(used byglance-canonical-layoutsto compose the current span's portrait and landscape orientations) andSizeMode.Responsive(used to pre-bundle a set ofDpSizebreakpoints) invokeprovideContentmultiple times per update. Decode shared bitmaps once inprovideGlancebeforeprovideContentand pass them toImageProvider(bitmap)soRemoteViewsdeduplicates the sharedBitmapacross all sizes in either mode. If a widget scales distinct bitmaps perLocalSize.current, dividecalculateWidgetMemoryCapBytes(context)by the number of distinct bitmaps.Downsample content URIs and grant launcher read permissions : On Android 17 (
targetSdk >= 37), bitmap-backedIcons count toward the cap alongsideBitmaps.content://URIs passed toImageProvider(uri)orRemoteViews.setImageViewUridon't count toward the inter-process communication (IPC) cap, but the launcher decodes them at full resolution. Downsample the file intocontext.cacheDir, expose it withFileProvider.getUriForFile, and grantIntent.FLAG_GRANT_READ_URI_PERMISSIONtoIntent.CATEGORY_HOMEpackages.Trigger explicit widget updates after state mutations :
updateAppWidgetStatewrites preferences to DataStore without updating the widget UI. After you mutate state, callGlanceAppWidget.update(context, glanceId)orGlanceAppWidget.updateAll(context).Use full updateAppWidget calls when you replace images : In
AppWidgetProviderclasses, when you replace bitmaps orIcons, callAppWidgetManager.updateAppWidgetinstead ofpartiallyUpdateAppWidget.
Reference implementation files
Read the companion reference files in references/ when implementing each step,
and update the sample package declaration and class names to match your app:
Bitmap Cap & FileProvider Helper : WidgetBitmapUtils.kt
Responsive Widget, Receiver & Error Fallback : SampleGlanceWidget.kt
LazyColumn Collection Widget & Revision Cache : FeedCollectionGlanceWidget.kt
DataStore State Push, CoroutineWorker & ActionCallback : WidgetStateAndActions.kt
Classic AppWidgetProvider WorkManager & Full Update : ClassicWidgetImageUpdater.kt
Manifest, Widget Info, FileProvider Paths & R8 Rules : AndroidManifest.xml, sample_widget_info.xml, file_paths.xml, proguard-rules.pro
Detailed steps
Step 1: Configure SDK levels, dependencies, and manifest declarations
Read
gradle/libs.versions.toml,app/build.gradle.kts, andapp/src/main/AndroidManifest.xmlfirst. If AGP, Kotlin, or library versions are already present, keep them; otherwise, addglance = "1.2.0"along withandroidx.glance:glance-appwidgetandandroidx.glance:glance-material3togradle/libs.versions.toml.In
app/build.gradle.kts, keepnamespace,applicationId, and any existingminSdk >= 26,targetSdk, or library versions. SetcompileSdk = 37(andtargetSdk = 37only when explicitly migrating the app to target Android 17), enablebuildFeatures { compose = true }, and addimplementation(libs.androidx.glance.appwidget)andimplementation(libs.androidx.glance.material3).In
AndroidManifest.xml(see AndroidManifest.xml), declare<queries>with<action android:name="android.intent.action.MAIN" />and<category android:name="android.intent.category.HOME" />soqueryIntentActivitiesdiscovers launcher packages on API 30+. Register yourGlanceAppWidgetReceiver(pointingandroid.appwidget.providerto@xml/sample_widget_info, see sample_widget_info.xml) and registerandroidx.core.content.FileProviderwithandroid:authorities="${applicationId}.fileprovider",android:exported="false",android:grantUriPermissions="true", andandroid.support.FILE_PROVIDER_PATHSpointing to@xml/file_paths(which declares<cache-path name="widget_cache" path="widget_images/" />, see file_paths.xml):
<br />
<receiver android:name=".glance.MyReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver><br />
Step 2: Estimate the widget memory cap and downsample images
Read WidgetBitmapUtils.kt and implement the memory cap and downsampling helpers:
Estimate the cap (
calculateWidgetMemoryCapBytes) : On API 30+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R), readval bounds = context.getSystemService(WindowManager::class.java)?.currentWindowMetrics?.boundsand, whenbounds != null && bounds.width() > 0 && bounds.height() > 0, return6L * bounds.width().toLong() * bounds.height().toLong()(callingWindowManagerfrom a non-UI widget or workerContextlogs a nonfatalStrictModeviolation on API 30+, which is expected for this estimate). On API 26-29 (or ifWindowManageris unavailable), fall back tocontext.resources.displayMetrics(6L * dm.widthPixels.toLong() * dm.heightPixels.toLong()). Becausesystem_servercaches6 * width * heightonce at boot, always size images to the widget'smaxWidthDp x maxHeightDpfirst (Stage 1) rather than filling the full screen budget.Downsample in two stages (
decodeDownsampledWidgetBitmap) :First, probe dimensions with
BitmapFactory.Options().apply { inJustDecodeBounds = true; inPreferredConfig = config }and doublesampleSize *= 2while(bounds.outWidth / (sampleSize * 2) >= targetWidthPx && bounds.outHeight / (sampleSize * 2) >= targetHeightPx) || (bounds.outWidth / sampleSize > targetWidthPx * 2) || (bounds.outHeight / sampleSize > targetHeightPx * 2)(wheretargetWidthPx = max(1, (maxWidthDp * density).toInt())), so wide banners or tall screenshots do not bypass downsampling when one axis already fits.Second, enforce the per-image byte ceiling (
perImageCapBytes = calculateWidgetMemoryCapBytes(context) / max(1, distinctImageCount)) by doublingsampleSize *= 2while(max(1, bounds.outWidth / sampleSize).toLong() * max(1, bounds.outHeight / sampleSize).toLong() * bytesPerPixel) > perImageCapBytes. Use2Lbytes per pixel forBitmap.Config.RGB_565(opaque photos),1LforBitmap.Config.ALPHA_8masks, and4Lwhenbounds.outConfig != null && bounds.outConfig != config(becauseBitmapFactoryupgrades translucent PNG or WebP files fromRGB_565to 4-byteARGB_8888). Decode withinSampleSize = sampleSizeandinPreferredConfig = config.
Choose between in-memory
BitmapandFileProviderUri(cacheWidgetImageUri) : UsedecodeDownsampledWidgetBitmapwithandroidx.glance.ImageProvider(bitmap)when passing bitmaps directly in memory. UsecacheWidgetImageUriwhen the widget passes acontent://UritoImageProvider(uri)orRemoteViews.setImageViewUri: it writes the downsampled bitmap intoFile(context.cacheDir, "widget_images"), obtains acontent://URI withFileProvider.getUriForFile(context, "${context.packageName}.fileprovider", outFile), queriesIntent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME), and callscontext.grantUriPermission(pkg, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION).
Step 3: Deduplicate multi-size bitmaps and handle composition errors
Read SampleGlanceWidget.kt to configure multi-size bitmap deduplication and composition error recovery:
Choose
SizeModefor your layout : UseSizeMode.Exact(recommended when usingglance-canonical-layoutsto render exactLocalSize.currentdimensions for the current grid span's portrait and landscape orientations),SizeMode.Responsive(RESPONSIVE_BUCKETS)(usingimport androidx.compose.ui.unit.DpSizeandimport androidx.compose.ui.unit.dpto pre-bundle fixedDpSizebreakpoints such assetOf(DpSize(120.dp, 120.dp), DpSize(240.dp, 120.dp), DpSize(240.dp, 240.dp))so resize transitions don't wake the app process), orSizeMode.Single(for a single layout).Hoist shared bitmap decodes before
provideContent: Because bothSizeMode.Exact(2-4orientation sizes) andSizeMode.Responsive(Nbuckets) runprovideContentmultiple times per update, decode shared bitmaps once inprovideGlancebeforeprovideContentwithdecodeDownsampledWidgetBitmapandBitmap.Config.RGB_565for opaque images (distinctImageCount = 1). Pass the resultingBitmaptoandroidx.glance.ImageProvider(sharedBitmap)soRemoteViewsdeduplicates the sharedBitmapacross all sizes.Provide a zero-bitmap error fallback : Override
onCompositionError(context, glanceId, appWidgetId, throwable)and callAppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, fallback)with a text-onlyRemoteViewslayout (0bitmaps).
Step 4: Pre-decode LazyColumn collection images and assign stable itemIds
Read FeedCollectionGlanceWidget.kt when rendering collection widgets with
LazyColumn:
Replace
import androidx.glance.appwidget.ImageProvider(which acceptsUri) withimport androidx.glance.ImageProvider(which acceptsBitmap). RemoveUri.fromFileandIcon.createWithContentUricalls.While a Glance session is active (~45 seconds after an update),
.update()and.updateAll()do not restartprovideGlance. Declarevar cachedRevision = -1andvar cachedThumbnails = emptyMap<String, Bitmap>()inprovideGlancebeforeprovideContent. InsideprovideContent, readval revision = currentState<Preferences>()[KEY_REVISION] ?: 0, and only whenrevision != cachedRevision, updatecachedRevision = revisionand decode thefeedItemsthumbnails withcachedThumbnails = feedItems.mapNotNull { item -> decodeDownsampledWidgetBitmap(..., distinctImageCount = max(1, feedItems.size), config = Bitmap.Config.RGB_565)?.let { item.id to it } }.toMap()(usemapNotNullrather thanassociateso the map staysMap<String, Bitmap>instead ofMap<String, Bitmap?>). This re-decodes when DataStore state updates during an active session while sharing one decodedMap<String, Bitmap>across allSizeMode.Responsivebuckets.In
LazyColumn, pass a stableLongitemIdtoitems(items = feedItems, itemId = { item -> item.id.hashCode().toLong() })(preferring a persistent numeric ID when available) and renderImage(provider = ImageProvider(bitmap), contentDescription = item.title)only whenbitmap != null(never fall back toUri.fromFilein anelsebranch).
Step 5: Push state updates, configure ActionCallbacks, and use WorkManager
Read WidgetStateAndActions.kt, proguard-rules.pro, and ClassicWidgetImageUpdater.kt to implement these patterns:
Push DataStore updates (requires
androidx.datastore:datastore-preferences-core:1.2.1andimport androidx.glance.appwidget.GlanceAppWidgetManager,import androidx.glance.appwidget.state.updateAppWidgetState, andimport androidx.glance.appwidget.updateAll): InpushWidgetRevisionUpdate(context)(see WidgetStateAndActions.kt), iterate overfor (glanceId in GlanceAppWidgetManager(context).getGlanceIds(Widget::class.java)), mutate state insideupdateAppWidgetState(context, glanceId) { prefs -> ... }(passing bothcontextandglanceId), and immediately callwidget.update(context, glanceId)(orwidget.updateAll(context)).Use zero-argument
ActionCallbackclasses withActionParameters.Keyand keep<init>()under R8 (requiresandroidx.work:work-runtime-ktx:2.12.0when scheduling background work):RunCallbackActioninstantiatesActionCallbackclasses withClass.getDeclaredConstructor().newInstance(), so a constructor parameter fails in every build. Remove constructor parameters fromActionCallbackimplementations, pass values withActionParameters.KeyandactionParametersOf(seeRefreshWidgetActionin WidgetStateAndActions.kt), and add-keep class * implements androidx.glance.appwidget.action.ActionCallback { public <init>(); }toproguard-rules.pro(see proguard-rules.pro). InsideSyncWidgetWorker.doWork(), callpushWidgetRevisionUpdate(applicationContext)so background work updates DataStore and refreshes the widget.Avoid unconditional one-time
WorkManagerenqueues inAppWidgetProvider.onUpdateand use fullupdateAppWidgetfor images (see ClassicWidgetImageUpdater.kt): Never callcontext.startForegroundServicefromAppWidgetProvider.onReceiveoronUpdate(which throwsForegroundServiceStartNotAllowedException).GlanceAppWidgetReceiveralready keeps a 10-year delayed session worker queued internally so WorkManager'sRescheduleReceivernever toggles. In a classicAppWidgetProvider, avoid enqueuing aOneTimeWorkRequestdirectly insideonUpdate: even withExistingWorkPolicy.KEEP, once the one-time worker finishes (SUCCEEDED),UnfinishedWorkListenerdisablesRescheduleReceiver, which broadcastsACTION_PACKAGE_CHANGEDand re-triggersonUpdatein a loop. Instead, schedule repeating syncs inonUpdatewithenqueueUniquePeriodicWork(..., ExistingPeriodicWorkPolicy.KEEP, ...)(which staysENQUEUEDacross runs), usegoAsync()for short< 10supdates, or enqueue one-time work (enqueueUniqueWork(..., ExistingWorkPolicy.KEEP, ...)) only from specific action broadcasts inonReceive. When you update aBitmaporIconon a classicRemoteViewswidget, callappWidgetManager.updateAppWidgetrather thanpartiallyUpdateAppWidget.
Common errors and fixes
| Symptom or exception | Root cause | Fix |
|---|---|---|
IllegalArgumentException: RemoteViews for widget update exceeds maximum bitmap memory usage | RemoteViews bitmap or icon memory exceeded 6 * W * H bytes. | Downsample to the widget's maxWidthDp x maxHeightDp with inSampleSize and RGB_565 (or ALPHA_8 for masks), enforce calculateWidgetMemoryCapBytes(context) / distinctImageCount, and decode shared bitmaps once before provideContent. |
SecurityException: Permission Denial: opening provider or blank content:// widget image | Home launcher lacks read permission for the FileProvider content:// URI or app passed file:// with Uri.fromFile. | Expose the file with FileProvider, add <queries> for CATEGORY_HOME, and call context.grantUriPermission with Intent.FLAG_GRANT_READ_URI_PERMISSION. |
Widget UI stays frozen after updateAppWidgetState | updateAppWidgetState writes to DataStore without recomposing, or provideGlance was not re-triggered during an active session. | Call .update(context, glanceId) or .updateAll(context) after updateAppWidgetState, and observe revision state inside provideContent. |
NoSuchMethodException: <Class>.<init> [] on action tap | ActionCallback declared constructor parameters or R8 stripped <init>(). | Use a public zero-argument constructor, pass values with ActionParameters.Key, and keep <init>() in ProGuard. |
ForegroundServiceStartNotAllowedException in receiver | AppWidgetProvider called startForegroundService in the background. | Schedule periodic syncs in onUpdate with enqueueUniquePeriodicWork (ExistingPeriodicWorkPolicy.KEEP) or enqueue one-time work (enqueueUniqueWork) from custom action broadcasts in onReceive. |
Unresolved reference: ImageProvider, DpSize, updateAppWidgetState, or updateAll | Missing or mismatched Glance and Compose package imports, or calling updateAppWidgetState(context) without glanceId. | Import androidx.glance.ImageProvider (for Bitmap), androidx.compose.ui.unit.DpSize and androidx.compose.ui.unit.dp (for SizeMode.Responsive), androidx.glance.appwidget.state.updateAppWidgetState (passing both context and glanceId), and androidx.glance.appwidget.updateAll. |
Antipatterns
NEVER decode or scale a
BitmapinsideprovideContentper size bucket withSizeMode.ResponsiveorSizeMode.Exact.NEVER size widget bitmaps to fill the full screen memory cap (
6 * width * height); always downsample to the widget's targetmaxWidthDp x maxHeightDpfirst so unfolding a foldable or switching display resolution after boot does not exceed the boot-time cap.NEVER pass an unscaled
content://UriorUri.fromFiletoImageProvider(uri)orRemoteViews.setImageViewUri, or omitcontext.grantUriPermissionforIntent.CATEGORY_HOME.NEVER call
updateAppWidgetStatewithout callingGlanceAppWidget.update(context, glanceId)orGlanceAppWidget.updateAll(context).NEVER call
partiallyUpdateAppWidgetwhen you replace widget images.
Files
- ATTRIBUTION.mdAdded by AutoGineer1016 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md20.8 KiB
- references/AndroidManifest.xml1.1 KiB
- references/ClassicWidgetImageUpdater.ktScript2.0 KiB
- references/FeedCollectionGlanceWidget.ktScript2.3 KiB
- references/SampleGlanceWidget.ktScript4.0 KiB
- references/WidgetBitmapUtils.ktScript4.4 KiB
- references/WidgetStateAndActions.ktScript1.8 KiB
- references/android/about/versions/17/behavior-changes-17.md24.1 KiB
- references/android/develop/ui/compose/glance/create-app-widget.md22.3 KiB
- references/android/develop/ui/views/appwidgets/index.md10.1 KiB
- references/proguard-rules.pro98 B
- references/res/xml/file_paths.xml117 B
- references/res/xml/sample_widget_info.xml307 B
references/android/develop/ui/compose/glance/create-app-widget.md
The following sections describe how to create a basic app widget with Glance.
[!IMPORTANT] Key Point: Glance provides a modern approach to build app widgets using Compose, but is restricted by the limitations of
AppWidgetsandRemoteViews. Therefore, Glance uses different composables from the Jetpack Compose UI.
Android skills
Manage AppWidget updates and bitmap memory
Use the AppWidget Updates and Memory Android skill to downsample widget bitmaps, deduplicate SizeMode.Responsive images, and stay within Android 17 RemoteViews memory limits. To install the skill from the Android CLI, run:
android skills add glance-appwidget-updates-and-memory<br />
Declare the AppWidget in the Manifest
After completing the setup steps, declare the AppWidget and its
metadata in your app.
Extend the
AppWidgetreceiver fromGlanceAppWidgetReceiver:class MyAppWidgetReceiver : GlanceAppWidgetReceiver() { override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget") }<br />
Register the provider of the app widget in your
AndroidManifest.xmlfile and the associated metadata file:<receiver android:name=".glance.MyReceiver" android:exported="true"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_app_widget_info" /> </receiver>
Add the AppWidgetProviderInfo metadata
Next, follow the Create a widget guide to create and define the app
widget info in the @xml/my_app_widget_info file.
The only difference for Glance is that there is no initialLayout XML, but you
must define one. You can use the predefined loading layout provided in the
library:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>Declare the AppWidgetProviderInfo XML
The AppWidgetProviderInfo object defines the essential qualities of your
widget. Define the AppWidgetProviderInfo in your XML metadata resource file
(res/xml/my_app_widget_info.xml) inside an <appwidget-provider> element:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:targetCellWidth="1"
android:targetCellHeight="1"
android:maxResizeWidth="250dp"
android:maxResizeHeight="120dp"
android:updatePeriodMillis="86400000"
android:description="@string/example_appwidget_description"
android:previewLayout="@layout/example_appwidget_preview"
android:initialLayout="@layout/glance_default_loading_layout"
android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"
android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>Widget sizing attributes
The default home screen positions widgets in its window based on a grid of cells that have a defined height and width. Most home screens only let widgets take on sizes that are integer multiples of the grid cells---for example, two cells horizontally by three cells vertically.
The widget sizing attributes let you specify a default size for your widget and provide lower and upper bounds on the size of the widget. In this context, the default size of a widget is the size that the widget takes on when it is first added to the home screen.
The following table describes the <appwidget-provider> attributes pertaining
to widget sizing:
| Attributes and description | |
|---|---|
targetCellWidth and targetCellHeight (Android 12), minWidth and minHeight | - Starting in Android 12, the targetCellWidth and targetCellHeight attributes specify the default size of the widget in terms of grid cells. These attributes are ignored in Android 11 and lower, and can be ignored if the home screen doesn't support a grid-based layout. - The minWidth and minHeight attributes specify the default size of the widget in dp. If the values for a widget's minimum width or height don't match the dimensions of the cells, then the values are rounded up to the nearest cell size. We recommend specifying both sets of attributes---targetCellWidth and targetCellHeight, and minWidth and minHeight---so that your app can fall back to using minWidth and minHeight if the user's device doesn't support targetCellWidth and targetCellHeight. If supported, the targetCellWidth and targetCellHeight attributes take precedence over the minWidth and minHeight attributes. |
minResizeWidth and minResizeHeight | Specify the widget's absolute minimum size. These values specify the size under which the widget is illegible or otherwise unusable. Using these attributes lets the user resize the widget to a size that is smaller than the default widget size. The minResizeWidth attribute is ignored if it is greater than minWidth or if horizontal resizing isn't enabled. See resizeMode. Likewise, the minResizeHeight attribute is ignored if it is greater than minHeight or if vertical resizing isn't enabled. |
maxResizeWidth and maxResizeHeight | Specify the widget's recommended maximum size. If the values aren't a multiple of the grid cell dimensions, they are rounded up to the nearest cell size. The maxResizeWidth attribute is ignored if it is smaller than minWidth or if horizontal resizing isn't enabled. See resizeMode. Likewise, the maxResizeHeight attribute is ignored if it is smaller than minHeight or if vertical resizing isn't enabled. Introduced in Android 12. |
resizeMode | Specifies the rules by which a widget can be resized. You can use this attribute to make home screen widgets resizable horizontally, vertically, or on both axes. Users touch & hold a widget to show its resize handles, then drag the horizontal or vertical handles to change its size on the layout grid. Values for the resizeMode attribute include horizontal, vertical, and none. To declare a widget as resizable horizontally and vertically, use `horizontal |
Example
To illustrate how the attributes in the preceding table affect widget sizing, assume the following specifications:
A grid cell is 30 dp wide and 50 dp tall.
The following attribute specification is provided:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="80dp" android:minHeight="80dp" android:targetCellWidth="2" android:targetCellHeight="2" android:minResizeWidth="40dp" android:minResizeHeight="40dp" android:maxResizeWidth="120dp" android:maxResizeHeight="120dp" android:resizeMode="horizontal|vertical" />
Starting with Android 12:
Use the targetCellWidth and targetCellHeight attributes as the default size
of the widget.
The widget's size is 2x2 by default. The widget can be resized down to 2x1 or up to 4x3.
Android 11 and lower:
Use the minWidth and minHeight attributes to compute the default size of the
widget.
The default width = Math.ceil(80 / 30) = 3
The default height = Math.ceil(80 / 50) = 2
The widget's size is 3x2 by default. The widget can be resized down to 2x1 or up to full screen.
[!NOTE] Note: Jetpack Glance handles layout changes inside
provideGlanceresponding to dynamic cell scaling if you useSizeMode.ResponsiveorSizeMode.Exact. See the Build UI with Glance guide to leverage these modes.
Additional widget attributes
The following table describes the <appwidget-provider> attributes pertaining
to qualities other than widget sizing.
| Attributes and description | |
|---|---|
updatePeriodMillis | Defines how often the widget framework requests an update from the GlanceAppWidgetReceiver by calling the onUpdate() callback method. We recommend updating as infrequently as possible---no more than once an hour---to conserve the battery. For details, see the When to update widgets section in Glance state management. |
initialLayout | Points to the layout resource that defines the loading layout of the widget before the Glance UI compositions render. You can use the predefined loading layout provided in the library: @layout/glance_default_loading_layout. |
configure | Defines the configuration activity that launches when the user adds the widget. See the Enable users to configure app widgets guide. |
description | Specifies the description for the widget picker to display for your widget. Introduced in Android 12. |
previewLayout (Android 12) and previewImage (Android 11 and lower) | - Starting in Android 12, the previewLayout attribute specifies a scalable preview, which you provide as an XML layout set to the widget's default size. Ideally, this points to a static XML mapping matching your design layout. - In Android 11 or lower, the previewImage attribute specifies a static image drawable screenshot of what the widget looks like, appearing in the widget picker. We recommend specifying both so your app falls back gracefully on older platforms. For newer platforms (Android 15+), you can define live generated previews in Kotlin using `GlanceAppWidget.providePreview`. See the Generated Previews guide. |
autoAdvanceViewId | Specifies the view ID of the widget subview that is auto-advanced by the widget's host. |
widgetCategory | Declares whether your widget can be displayed on the home screen (home_screen), the lock screen (keyguard), or both. For Android 5.0 and higher, only home_screen is valid. |
widgetFeatures | Declares features supported by the widget. For example, if your widget's configuration is optional, specify both configuration_optional and reconfigurable. |
Define GlanceAppWidget
Create a new class that extends from
GlanceAppWidgetand overrides theprovideGlancemethod. This is the method where you can load data that is needed to render your widget:[!NOTE] Note:
provideGlanceruns on the main thread. To perform any long running operations inprovideGlance, switch to another thread usingwithContext. See Use coroutines for main-safety for more details on how to run outside of the main thread.class MyAppWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { // In this method, load data needed to render the AppWidget. // Use `withContext` to switch to another thread for long running // operations. provideContent { // create your AppWidget here Text("Hello World") } } }<br />
Instantiate it in the
glanceAppWidgeton yourGlanceAppWidgetReceiver:class MyAppWidgetReceiver : GlanceAppWidgetReceiver() { // Let MyAppWidgetReceiver know which GlanceAppWidget to use override val glanceAppWidget: GlanceAppWidget = MyAppWidget() }<br />
You've now configured an AppWidget using Glance.
[!NOTE] Note: Calling
updateAppWidgetStatewrites updated preferences to the backingDataStore, but it doesn't automatically recompose your widget or push a newRemoteViewshierarchy to the launcher. Always callMyAppWidget().update(context, glanceId)orMyAppWidget().updateAll(context)afterupdateAppWidgetStatecompletes---especially insideWorkManagerCoroutineWorkertasks andActionCallbackhandlers.
Manage widget image memory across size buckets
When your widget displays bitmaps or content:// image URIs, keep total
serialized RemoteViews image memory within the system limit (6 * width * height bytes, enforced with an IllegalArgumentException on Android 17 and
higher) by doing the following:
Decode shared bitmaps before
provideContent: If your widget usesSizeMode.ResponsiveorSizeMode.Exact, Glance runs theprovideContentlambda once for eachDpSizebucket. Decoding aBitmapinsideprovideContentallocates a separateBitmapinstance for every bucket, multiplying memory usage inRemoteViews.mBitmapCache. Decode downsampled bitmaps (Bitmap.Config.RGB_565for opaque images) once insideprovideGlancebeforeprovideContentso all buckets share a singleBitmapreference.Use the matching
ImageProviderimport: Jetpack Glance provides two distinctImageProvideroverloads across two packages:Use
import androidx.glance.ImageProviderwhen passing an in-memoryImageProvider(bitmap: Bitmap)or drawable resourceImageProvider(resId: Int).Use
import androidx.glance.appwidget.ImageProviderwhen passing a URIImageProvider(uri: Uri). When serving cached files through aFileProviderURI, grantIntent.FLAG_GRANT_READ_URI_PERMISSIONto every installedIntent.CATEGORY_HOMElauncher package so the host can render the image.
Use the GlanceAppWidgetReceiver class to handle widget broadcasts
The GlanceAppWidgetReceiver coordinates widget broadcasts and platform state
updates by extending the underlying AppWidgetProvider. It receives
platform events when your widget is updated, deleted, enabled, or disabled,
translating them into Compose lifecycle requests.
Declare a widget in the manifest
Declare your GlanceAppWidgetReceiver class subclass as a broadcast receiver in
your AndroidManifest.xml file:
<receiver android:name="MyReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver>The <receiver> element requires the android:name attribute, which specifies
the receiver class. The receiver must accept the ACTION_APPWIDGET_UPDATE
broadcast action inside the <intent-filter>.
The <meta-data> element must identify its name as
android.appwidget.provider, and the android:resource attribute must point to
your AppWidgetProviderInfo XML metadata resource (@xml/my_app_widget_info).
Implement the GlanceAppWidgetReceiver class
In Glance, you extend GlanceAppWidgetReceiver instead of
AppWidgetProvider directly. Implement it by linking your receiver to your
GlanceAppWidget instance. The primary callbacks available in
GlanceAppWidgetReceiver operate as follows:
onUpdate(): Automatically overridden by Glance to execute composition updates. If you manually overrideonUpdate, you must callsuper.onUpdateto allow Glance to successfully launch composition threads.onAppWidgetOptionsChanged(): Called when the widget is first placed or resized. Glance reads options bundle items under the hood so your layout adjusts seamlessly based on runtime dimensions.onDeleted(Context, IntArray): Invoked whenever a specific widget instance is deleted by the user.onEnabled(Context): Triggered when the first instance of your widget is successfully created. Excellent for running global migrations.onDisabled(Context): Called when the last active instance of the provider is removed.onReceive(Context, Intent): Intercepts every platform broadcast before specific callback methods. You must ensure that any custom receiver logic you write callssuper.onReceive(context, intent)and must never callgoAsyncyourself since Glance automatically routes work asynchronously.
[!WARNING] Warning: Because receivers are subject to strict 10-second background limits , long-running actions should never run in receiver callbacks. If you need to perform complex database queries or network lookups, delegate the task to
WorkManagerand executeGlanceAppWidget().update()upon completion.
Receive widget broadcast intents
Under the hood, GlanceAppWidgetReceiver filters and handles the following
foundational platform widget broadcast intents:
Create UI
The following snippet demonstrates how to create the UI:
/* Import Glance Composables
In the event there is a name clash with the Compose classes of the same name,
you may rename the imports per https://kotlinlang.org/docs/packages.html#imports
using the `as` keyword.
import androidx.glance.Button
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.text.Text
*/
class MyAppWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
// Load data needed to render the AppWidget.
// Use `withContext` to switch to another thread for long running
// operations.
provideContent {
// create your AppWidget here
MyContent()
}
}
@Composable
private fun MyContent() {
Column(
modifier = GlanceModifier.fillMaxSize(),
verticalAlignment = Alignment.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp))
Row(horizontalAlignment = Alignment.CenterHorizontally) {
Button(
text = "Home",
onClick = actionStartActivity<MyActivity>()
)
Button(
text = "Work",
onClick = actionStartActivity<MyActivity>()
)
}
}
}
}<br />
The preceding code sample does the following:
In the top level
Column, items are placed vertically one after each other.The
Columnexpands its size to match the available space (via theGlanceModifierand aligns its content to the top (verticalAlignment) and centers it horizontally (horizontalAlignment).The
Column's content is defined using the lambda. The order matters.The first item in the
Columnis aTextcomponent with12.dpof padding.The second item is a
Row, where items are placed horizontally one after each other, with twoButtonscentered horizontally (horizontalAlignment). The final display depends on the available space. The following image is an example of what it may look like:
Image: destination_widget Figure 1. An example UI.
You can change the alignment values or apply different modifier values (such as padding) to change the placement and size of the components. See the reference documentation for a full list of components, parameters, and available modifiers for each class.
Implement rounded corners
Android 12 introduces system parameters to customize the corner radii of your app widgets dynamically:
system_app_widget_background_radius: Specifies the corner radius of the widget background container (never larger than 28 dp).Inner radius: To prevent content clipping, calculate a proportional radius for your inner content based on the system background outline:
systemRadiusValue - widgetPadding
In Glance, you can apply corner radius sizing properties dynamically in
composition using
GlanceModifier.cornerRadius(android.R.dimen.system_app_widget_background_radius).
For backward compatibility on devices running Android 11 (API level 30) or lower, implement custom attributes and custom theme resource fallbacks:
/values/attrs.xml<resources> <attr name="backgroundRadius" format="dimension" /> </resources>/values/styles.xml<resources> <style name="MyWidgetTheme"> <item name="backgroundRadius">@dimen/my_background_radius_dimen</item> </style> </resources>/values-31/styles.xml<resources> <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight"> <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item> </style> </resources>/drawable/my_widget_background.xml<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="?attr/backgroundRadius" /> </shape>
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
What our scanner noticed· Low risk
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Google LLC
- Publisher
- Android (Google) (Official)
- Distributed by
- Android (Google)
- Source
- android/skills · system/glance-appwidget-updates-and-memory · commit bf68e2d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.txt)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download glance-appwidget-updates-and-memory.zip