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://mp-staging.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/views/appwidgets/index.md
Try the Compose way Jetpack Compose is the recommended UI toolkit for Android. Learn how to build widgets using Compose-style APIs. Jetpack Glance → Image: untitled
<br />
App widgets are miniature app views that you can embed in other apps---such as the home screen---and receive periodic updates. These views are referred to as widgets in the user interface, and you can publish one with an app widget provider (or widget provider ). An app component that holds other widgets is called an app widget host (or widget host). Figure 1 shows a sample music widget: Image: Example of music widget Figure 1. Example of a music widget.
This document describes how to publish a widget using a widget provider. For
details about creating your own AppWidgetHost to host
app widgets, see Build a widget host.
For information about how to design your widget, see App widgets overview.
Android skills
Manage AppWidget updates and bitmap memory
Use the AppWidget Updates and Memory Android skill to downsample widget bitmaps, deduplicate responsive images, and stay within Android 17 RemoteViews memory limits across Glance and classic AppWidgetProvider widgets. To install the skill from the Android CLI, run:
android skills add glance-appwidget-updates-and-memory<br />
Widget components
To create a widget, you need the following basic components:
AppWidgetProviderInfo object
: Describes the metadata for a widget, such
as the widget's layout, update frequency, and AppWidgetProvider
class. AppWidgetProviderInfo is defined in
XML, as described in this document.
AppWidgetProvider class
: Defines the basic methods
that let you programmatically interface with the widget. Through it, you
receive broadcasts when the widget is updated, enabled, disabled, or
deleted. You declare AppWidgetProvider in the manifest and then
implement it, as described in this document.
View layout : Defines the initial layout for the widget. The layout is defined in XML, as described in this document.
Figure 2 shows how these components fit into the overall app widget processing flow. Image: App widget processing flow Figure 2. App widget processing flow.
[!NOTE] Note: Android Studio can automatically create a set of
AppWidgetProviderInfo,AppWidgetProvider, and view layout files. Choose New > Widget > App Widget.
If your widget needs user configuration, implement the app widget configuration activity. This activity lets users modify widget settings---for example, the time zone for a clock widget.
Starting in Android 12 (API level 31), you can provide a default configuration and let users reconfigure the widget later. See Use the widget's default configuration and Enable users to reconfigure placed widgets for more details.
In Android 11 (API level 30) or lower, this activity is launched every time the user adds the widget to their home screen.
We also recommend the following improvements: flexible widget layouts, miscellaneous enhancements, advanced widgets, collection widgets, and building a widget host.
Declare the AppWidgetProviderInfo XML
Defining the metadata settings (such as default cell sizes, resize constraints, and update frequencies) is exactly identical across both traditional Views and Glance-based widgets.
To learn how to define and configure your metadata XML file, see the Compose-first Declare the AppWidgetProviderInfo XML section in the Glance documentation.
Use the AppWidgetProvider class to handle widget broadcasts
The platform's broadcast receiver mechanics, manifest declaration filters, and
lifecycle event loops are unified underneath the platform. In Compose-first
development, these broadcasts are orchestrated using the
GlanceAppWidgetReceiver wrapper.
To understand how to register your receiver in the manifest and implement Hilt-compatible lifecycle overrides, see the Compose-first Use the AppWidgetProvider class to handle broadcasts section in the Glance documentation.
Create the widget layout
You must define an initial layout for your widget in XML and save it in the
project's res/layout/ directory. Refer to Design guidelines for details.
Creating the widget layout is straightforward if you're familiar with
layouts. However, be aware that widget layouts are based on
RemoteViews, which doesn't support every kind of layout or view widget.
You can't use custom views or subclasses of the views that are supported by
RemoteViews.
RemoteViews also supports ViewStub, which is an invisible, zero-sized
View you can use to lazily inflate layout resources at runtime.
Support for stateful behavior
Android 12 adds support for stateful behavior using the following existing components:
The widget is still stateless. Your app must store the state and register for state change events. Image: Example of shopping list widget showing stateful behavior Figure 3. Example of stateful behavior.
[!NOTE] Note: Always explicitly set the current checked state using
RemoteViews.setCompoundButtonChecked, or you might encounter unexpected results when your widget is dragged or resized.
The following code example shows how to implement these components.
// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true)
// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2)
// Listen for check changes. The intent has an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
R.id.my_checkbox,
RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent)
)<br />
Provide two layouts: one targeting devices running Android 12 or
higher in res/layout-v31, and the other targeting previous
Android 11 or lower in the default res/layout folder.
Implement rounded corners
Calculating the outer background and inner proportional radii is standard and shared. In Compose-first development, this can be set dynamically in Kotlin alongside custom theme resources.
To implement corner radii or set up dynamic styles for older Android devices, see the Compose-first Implement rounded corners section in the Glance documentation.
Keep RemoteViews images within system memory limits
Widgets run inside the home screen launcher process, and system_server
enforces a per-widget bitmap memory ceiling (6 * width * height bytes; see the
Android 17 widget memory limit behavior change). When building classic
AppWidgetProvider widgets with RemoteViews, do the following:
Downsample bitmaps using
WindowMetrics: On API level 30 and higher, compute your maximum bitmap budget fromWindowManager.currentWindowMetrics.boundsrather thanresources.displayMetrics, and decode opaque images withBitmap.Config.RGB_565and power-of-twoinSampleSize.Use full
updateAppWidgetcalls when replacing images: Avoid callingpartiallyUpdateAppWidgetto updateImageViewbitmaps orIconURIs, because partial updates append actions to the cachedRemoteViewshierarchy insystem_serverwithout releasing prior image allocations.Enqueue background work with
WorkManager: Don't callContext.startForegroundServicefromAppWidgetProvider.onReceiveoronUpdate, which throwsForegroundServiceStartNotAllowedExceptionwhen triggered in the background. Enqueue aWorkManagerCoroutineWorkerinstead, and passcontext.applicationContextwhen inflating or updatingRemoteViewsfrom background workers.
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