Generated banner for Glance Appwidget Updates And Memory
Generated logo for Glance Appwidget Updates And Memory

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

From the author

Manages Android AppWidget state updates, background syncs, and RemoteViews bitmap memory limits with Jetpack Glance (androidx.glance:glance-appwidget) and AppWidgetProvider receivers on Android 17 (API 37). Use this skill when configuring Android 17 (API level 37) compileSdk/targetSdk and AndroidManifest.xml widget receivers, refreshing frozen or stale Glance widget UI after CoroutineWorker DataStore preference updates (updateAppWidgetState), downsampling images within the WindowMetrics memory limit for SizeMode.Responsive, SizeMode.Exact, or LazyColumn, sharing content:// URIs with FileProvider, or fixing ActionCallback NoSuchMethodException and AppWidgetProvider ForegroundServiceStartNotAllowedException crashes with WorkManager. Don't use for Glance layout templates (suggest the glance-canonical-layouts skill) or in-app Compose screens.
— the description by Google LLC

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

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/android/glance-appwidget-updates-and-memory/download
  2. Unzip 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.

Prerequisites

  • Kotlin Android project (minSdk >= 26) with compileSdk = 37 (targetSdk = 37 when targeting Android 17) in build.gradle.kts.

  • Jetpack Glance (1.2.0, minimum 1.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 existing namespace, 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_server computes the RemoteViews bitmap memory cap (6 * width * height bytes, or 1.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 to QHD+ 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 maxHeightDp in RGB_565 or ALPHA_8), and enforce the per-image byte ceiling (calculateWidgetMemoryCapBytes(context) / distinctImageCount) estimated as 6 * width * height from WindowManager.currentWindowMetrics.bounds on API 30+ (falling back to resources.displayMetrics on API 26-29 or if WindowManager is unavailable).

  • Decode shared bitmaps once outside provideContent : Both SizeMode.Exact (used by glance-canonical-layouts to compose the current span's portrait and landscape orientations) and SizeMode.Responsive (used to pre-bundle a set of DpSize breakpoints) invoke provideContent multiple times per update. Decode shared bitmaps once in provideGlance before provideContent and pass them to ImageProvider(bitmap) so RemoteViews deduplicates the shared Bitmap across all sizes in either mode. If a widget scales distinct bitmaps per LocalSize.current, divide calculateWidgetMemoryCapBytes(context) by the number of distinct bitmaps.

  • Downsample content URIs and grant launcher read permissions : On Android 17 (targetSdk >= 37), bitmap-backed Icons count toward the cap alongside Bitmaps. content:// URIs passed to ImageProvider(uri) or RemoteViews.setImageViewUri don't count toward the inter-process communication (IPC) cap, but the launcher decodes them at full resolution. Downsample the file into context.cacheDir, expose it with FileProvider.getUriForFile, and grant Intent.FLAG_GRANT_READ_URI_PERMISSION to Intent.CATEGORY_HOME packages.

  • Trigger explicit widget updates after state mutations : updateAppWidgetState writes preferences to DataStore without updating the widget UI. After you mutate state, call GlanceAppWidget.update(context, glanceId) or GlanceAppWidget.updateAll(context).

  • Use full updateAppWidget calls when you replace images : In AppWidgetProvider classes, when you replace bitmaps or Icons, call AppWidgetManager.updateAppWidget instead of partiallyUpdateAppWidget.

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:

Detailed steps

Step 1: Configure SDK levels, dependencies, and manifest declarations
  1. Read gradle/libs.versions.toml, app/build.gradle.kts, and app/src/main/AndroidManifest.xml first. If AGP, Kotlin, or library versions are already present, keep them; otherwise, add glance = "1.2.0" along with androidx.glance:glance-appwidget and androidx.glance:glance-material3 to gradle/libs.versions.toml.

  2. In app/build.gradle.kts, keep namespace, applicationId, and any existing minSdk >= 26, targetSdk, or library versions. Set compileSdk = 37 (and targetSdk = 37 only when explicitly migrating the app to target Android 17), enable buildFeatures { compose = true }, and add implementation(libs.androidx.glance.appwidget) and implementation(libs.androidx.glance.material3).

  3. In AndroidManifest.xml (see AndroidManifest.xml), declare <queries> with <action android:name="android.intent.action.MAIN" /> and <category android:name="android.intent.category.HOME" /> so queryIntentActivities discovers launcher packages on API 30+. Register your GlanceAppWidgetReceiver (pointing android.appwidget.provider to @xml/sample_widget_info, see sample_widget_info.xml) and register androidx.core.content.FileProvider with android:authorities="${applicationId}.fileprovider", android:exported="false", android:grantUriPermissions="true", and android.support.FILE_PROVIDER_PATHS pointing 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:

  1. Estimate the cap (calculateWidgetMemoryCapBytes) : On API 30+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R), read val bounds = context.getSystemService(WindowManager::class.java)?.currentWindowMetrics?.bounds and, when bounds != null && bounds.width() > 0 && bounds.height() > 0, return 6L * bounds.width().toLong() * bounds.height().toLong() (calling WindowManager from a non-UI widget or worker Context logs a nonfatal StrictMode violation on API 30+, which is expected for this estimate). On API 26-29 (or if WindowManager is unavailable), fall back to context.resources.displayMetrics (6L * dm.widthPixels.toLong() * dm.heightPixels.toLong()). Because system_server caches 6 * width * height once at boot, always size images to the widget's maxWidthDp x maxHeightDp first (Stage 1) rather than filling the full screen budget.

  2. Downsample in two stages (decodeDownsampledWidgetBitmap) :

    • First, probe dimensions with BitmapFactory.Options().apply { inJustDecodeBounds = true; inPreferredConfig = config } and double sampleSize *= 2 while (bounds.outWidth / (sampleSize * 2) >= targetWidthPx && bounds.outHeight / (sampleSize * 2) >= targetHeightPx) || (bounds.outWidth / sampleSize > targetWidthPx * 2) || (bounds.outHeight / sampleSize > targetHeightPx * 2) (where targetWidthPx = 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 doubling sampleSize *= 2 while (max(1, bounds.outWidth / sampleSize).toLong() * max(1, bounds.outHeight / sampleSize).toLong() * bytesPerPixel) > perImageCapBytes. Use 2L bytes per pixel for Bitmap.Config.RGB_565 (opaque photos), 1L for Bitmap.Config.ALPHA_8 masks, and 4L when bounds.outConfig != null && bounds.outConfig != config (because BitmapFactory upgrades translucent PNG or WebP files from RGB_565 to 4-byte ARGB_8888). Decode with inSampleSize = sampleSize and inPreferredConfig = config.

  3. Choose between in-memory Bitmap and FileProvider Uri (cacheWidgetImageUri) : Use decodeDownsampledWidgetBitmap with androidx.glance.ImageProvider(bitmap) when passing bitmaps directly in memory. Use cacheWidgetImageUri when the widget passes a content:// Uri to ImageProvider(uri) or RemoteViews.setImageViewUri: it writes the downsampled bitmap into File(context.cacheDir, "widget_images"), obtains a content:// URI with FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", outFile), queries Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME), and calls context.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:

  1. Choose SizeMode for your layout : Use SizeMode.Exact (recommended when using glance-canonical-layouts to render exact LocalSize.current dimensions for the current grid span's portrait and landscape orientations), SizeMode.Responsive(RESPONSIVE_BUCKETS) (using import androidx.compose.ui.unit.DpSize and import androidx.compose.ui.unit.dp to pre-bundle fixed DpSize breakpoints such as setOf(DpSize(120.dp, 120.dp), DpSize(240.dp, 120.dp), DpSize(240.dp, 240.dp)) so resize transitions don't wake the app process), or SizeMode.Single (for a single layout).

  2. Hoist shared bitmap decodes before provideContent : Because both SizeMode.Exact (2-4 orientation sizes) and SizeMode.Responsive (N buckets) run provideContent multiple times per update, decode shared bitmaps once in provideGlance before provideContent with decodeDownsampledWidgetBitmap and Bitmap.Config.RGB_565 for opaque images (distinctImageCount = 1). Pass the resulting Bitmap to androidx.glance.ImageProvider(sharedBitmap) so RemoteViews deduplicates the shared Bitmap across all sizes.

  3. Provide a zero-bitmap error fallback : Override onCompositionError(context, glanceId, appWidgetId, throwable) and call AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, fallback) with a text-only RemoteViews layout (0 bitmaps).

Step 4: Pre-decode LazyColumn collection images and assign stable itemIds

Read FeedCollectionGlanceWidget.kt when rendering collection widgets with LazyColumn:

  1. Replace import androidx.glance.appwidget.ImageProvider (which accepts Uri) with import androidx.glance.ImageProvider (which accepts Bitmap). Remove Uri.fromFile and Icon.createWithContentUri calls.

  2. While a Glance session is active (~45 seconds after an update), .update() and .updateAll() do not restart provideGlance. Declare var cachedRevision = -1 and var cachedThumbnails = emptyMap<String, Bitmap>() in provideGlance before provideContent. Inside provideContent, read val revision = currentState<Preferences>()[KEY_REVISION] ?: 0, and only when revision != cachedRevision, update cachedRevision = revision and decode the feedItems thumbnails with cachedThumbnails = feedItems.mapNotNull { item -> decodeDownsampledWidgetBitmap(..., distinctImageCount = max(1, feedItems.size), config = Bitmap.Config.RGB_565)?.let { item.id to it } }.toMap() (use mapNotNull rather than associate so the map stays Map<String, Bitmap> instead of Map<String, Bitmap?>). This re-decodes when DataStore state updates during an active session while sharing one decoded Map<String, Bitmap> across all SizeMode.Responsive buckets.

  3. In LazyColumn, pass a stable Long itemId to items(items = feedItems, itemId = { item -> item.id.hashCode().toLong() }) (preferring a persistent numeric ID when available) and render Image(provider = ImageProvider(bitmap), contentDescription = item.title) only when bitmap != null (never fall back to Uri.fromFile in an else branch).

Step 5: Push state updates, configure ActionCallbacks, and use WorkManager

Read WidgetStateAndActions.kt, proguard-rules.pro, and ClassicWidgetImageUpdater.kt to implement these patterns:

  1. Push DataStore updates (requires androidx.datastore:datastore-preferences-core:1.2.1 and import androidx.glance.appwidget.GlanceAppWidgetManager, import androidx.glance.appwidget.state.updateAppWidgetState, and import androidx.glance.appwidget.updateAll): In pushWidgetRevisionUpdate(context) (see WidgetStateAndActions.kt), iterate over for (glanceId in GlanceAppWidgetManager(context).getGlanceIds(Widget::class.java)), mutate state inside updateAppWidgetState(context, glanceId) { prefs -> ... } (passing both context and glanceId), and immediately call widget.update(context, glanceId) (or widget.updateAll(context)).

  2. Use zero-argument ActionCallback classes with ActionParameters.Key and keep <init>() under R8 (requires androidx.work:work-runtime-ktx:2.12.0 when scheduling background work): RunCallbackAction instantiates ActionCallback classes with Class.getDeclaredConstructor().newInstance(), so a constructor parameter fails in every build. Remove constructor parameters from ActionCallback implementations, pass values with ActionParameters.Key and actionParametersOf (see RefreshWidgetAction in WidgetStateAndActions.kt), and add -keep class * implements androidx.glance.appwidget.action.ActionCallback { public <init>(); } to proguard-rules.pro (see proguard-rules.pro). Inside SyncWidgetWorker.doWork(), call pushWidgetRevisionUpdate(applicationContext) so background work updates DataStore and refreshes the widget.

  3. Avoid unconditional one-time WorkManager enqueues in AppWidgetProvider.onUpdate and use full updateAppWidget for images (see ClassicWidgetImageUpdater.kt): Never call context.startForegroundService from AppWidgetProvider.onReceive or onUpdate (which throws ForegroundServiceStartNotAllowedException). GlanceAppWidgetReceiver already keeps a 10-year delayed session worker queued internally so WorkManager's RescheduleReceiver never toggles. In a classic AppWidgetProvider, avoid enqueuing a OneTimeWorkRequest directly inside onUpdate: even with ExistingWorkPolicy.KEEP, once the one-time worker finishes (SUCCEEDED), UnfinishedWorkListener disables RescheduleReceiver, which broadcasts ACTION_PACKAGE_CHANGED and re-triggers onUpdate in a loop. Instead, schedule repeating syncs in onUpdate with enqueueUniquePeriodicWork(..., ExistingPeriodicWorkPolicy.KEEP, ...) (which stays ENQUEUED across runs), use goAsync() for short < 10s updates, or enqueue one-time work (enqueueUniqueWork(..., ExistingWorkPolicy.KEEP, ...)) only from specific action broadcasts in onReceive. When you update a Bitmap or Icon on a classic RemoteViews widget, call appWidgetManager.updateAppWidget rather than partiallyUpdateAppWidget.

Common errors and fixes

Symptom or exceptionRoot causeFix
IllegalArgumentException: RemoteViews for widget update exceeds maximum bitmap memory usageRemoteViews 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 imageHome 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 updateAppWidgetStateupdateAppWidgetState 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 tapActionCallback 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 receiverAppWidgetProvider 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 updateAllMissing 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 Bitmap inside provideContent per size bucket with SizeMode.Responsive or SizeMode.Exact.

  • NEVER size widget bitmaps to fill the full screen memory cap (6 * width * height); always downsample to the widget's target maxWidthDp x maxHeightDp first so unfolding a foldable or switching display resolution after boot does not exceed the boot-time cap.

  • NEVER pass an unscaled content:// Uri or Uri.fromFile to ImageProvider(uri) or RemoteViews.setImageViewUri, or omit context.grantUriPermission for Intent.CATEGORY_HOME.

  • NEVER call updateAppWidgetState without calling GlanceAppWidget.update(context, glanceId) or GlanceAppWidget.updateAll(context).

  • NEVER call partiallyUpdateAppWidget when you replace widget images.

Files

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 AppWidgets and RemoteViews. Therefore, Glance uses different composables from the Jetpack Compose UI.

Android skills

View on GitHub

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.

  1. Extend the AppWidget receiver from GlanceAppWidgetReceiver:

    class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
        override val glanceAppWidget: GlanceAppWidget = TODO("Create GlanceAppWidget")
    }

    <br />

  2. Register the provider of the app widget in your AndroidManifest.xml file 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 minResizeHeightSpecify 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 maxResizeHeightSpecify 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.
resizeModeSpecifies 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 provideGlance responding to dynamic cell scaling if you use SizeMode.Responsive or SizeMode.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
updatePeriodMillisDefines 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.
initialLayoutPoints 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.
configureDefines the configuration activity that launches when the user adds the widget. See the Enable users to configure app widgets guide.
descriptionSpecifies 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.
autoAdvanceViewIdSpecifies the view ID of the widget subview that is auto-advanced by the widget's host.
widgetCategoryDeclares 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.
widgetFeaturesDeclares features supported by the widget. For example, if your widget's configuration is optional, specify both configuration_optional and reconfigurable.

Define GlanceAppWidget

  1. Create a new class that extends from GlanceAppWidget and overrides the provideGlance method. This is the method where you can load data that is needed to render your widget:

    [!NOTE] Note: provideGlance runs on the main thread. To perform any long running operations in provideGlance, switch to another thread using withContext. 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 />

  2. Instantiate it in the glanceAppWidget on your GlanceAppWidgetReceiver:

    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 updateAppWidgetState writes updated preferences to the backing DataStore, but it doesn't automatically recompose your widget or push a new RemoteViews hierarchy to the launcher. Always call MyAppWidget().update(context, glanceId) or MyAppWidget().updateAll(context) after updateAppWidgetState completes---especially inside WorkManager CoroutineWorker tasks and ActionCallback handlers.

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 uses SizeMode.Responsive or SizeMode.Exact, Glance runs the provideContent lambda once for each DpSize bucket. Decoding a Bitmap inside provideContent allocates a separate Bitmap instance for every bucket, multiplying memory usage in RemoteViews.mBitmapCache. Decode downsampled bitmaps (Bitmap.Config.RGB_565 for opaque images) once inside provideGlance before provideContent so all buckets share a single Bitmap reference.

  • Use the matching ImageProvider import: Jetpack Glance provides two distinct ImageProvider overloads across two packages:

    • Use import androidx.glance.ImageProvider when passing an in-memory ImageProvider(bitmap: Bitmap) or drawable resource ImageProvider(resId: Int).

    • Use import androidx.glance.appwidget.ImageProvider when passing a URI ImageProvider(uri: Uri). When serving cached files through a FileProvider URI, grant Intent.FLAG_GRANT_READ_URI_PERMISSION to every installed Intent.CATEGORY_HOME launcher 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 override onUpdate, you must call super.onUpdate to 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 calls super.onReceive(context, intent) and must never call goAsync yourself 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 WorkManager and execute GlanceAppWidget().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 Column expands its size to match the available space (via the GlanceModifier and 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 Column is a Text component with 12.dp of padding.

    • The second item is a Row, where items are placed horizontally one after each other, with two Buttons centered 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