glance_widget_android 2.0.1
glance_widget_android: ^2.0.1 copied to clipboard
Android implementation of the glance_widget plugin using Jetpack Glance.
2.0.1 #
Added: an example/. This package is endorsed, so an app never calls it
directly and the runnable example belongs to glance_widget; what is specific
to Android is the manifest, and that is what the example points at -- including
the android:exported="true" receiver declaration, which installs without
complaint when it is wrong and then never reaches the widget picker. No code
change.
2.0.0 #
Added: getWidgetData. Every update now writes a record of the payload
under the app's own widget id, alongside the tracked id and with the same
lifetime, so forgetWidget and the home screen removal from #13 drop both. The
payload is stored exactly as it arrived rather than rebuilt from the Glance
state it was spread across, which would have meant a second copy of the field
mapping here. See #37.
Unchanged, deliberately: the Live Activity methods added in this release
are not implemented here. The plugin answers them with notImplemented, which
Dart turns into an UnsupportedError. Android 16's Live Updates
(ProgressStyle notifications) are the nearest equivalent and are a
notification rather than a widget; standing in for one would put nothing on a
home screen while reporting success.
Fixed: all seven templates now override GlanceAppWidget.onDelete, which
Glance calls before it deletes an instance's own state -- the only moment at
which the widgetId the instance carried is still readable. It drops the
cached image and the tracked id, neither of which Glance knows about. The
default onDelete is a no-op, so a template without one compiles, installs and
leaks in silence; TemplateDeleteHookTest fails on an eighth template added
without the override.
Fixed: removeWidgetId had no callers. The intent was there and the
onDelete that should have driven it was not, so the only way off the active
list was to uninstall the app.
Changed: getActiveWidgetIds is sorted. It returned activeWidgetIds .toList(), and a LinkedHashSet iterates in insertion order -- so the order
was whichever sequence of updates the app happened to make, and different again
after a reload from SharedPreferences.
Fixed: every template overflowed the smallest slot a user can drag it to.
Each *_widget_info.xml declares a minResizeHeight well under its
minHeight, and the templates painted the same layout into both. At each
one's own declared floor:
| Template | Fixed vertical cost | Slot | Left for content |
|---|---|---|---|
| Progress (circular) | 32dp padding + 18 title + 12 + 80dp dial + 8 + 16 = 166dp | 110dp | -56dp |
| Gauge (dashboard) | 32 + 21 + 8 + one 70dp row = 131dp | 110dp | -21dp, and rows two onward were drawn invisibly |
| Simple | 32 + 18 title + 8 + 28sp value = ~90dp | 40dp | -50dp |
| Calendar | 32 + 56dp date block + 12 + divider + 8 = 109dp | 110dp | 1dp for the events |
| Image | 32 + 29 title + 44 caption = 105dp | 110dp | 5dp for the picture |
| Chart | 32 + 29 header + 26 subtitle = 87dp | 110dp | 23dp for the plot |
Nothing scrolls, so the overflow was cut off the bottom with nothing on screen
to say it existed. Each template now reads LocalSize and picks one of three
layouts. ListWidget was already safe -- its LazyColumn scrolls -- but it
now spends less of a short slot on a header it can restate.
Added: WidgetTypeScale, one type and spacing scale for all seven
templates. They each carried their own literals, which is how widgets on the
same home screen end up disagreeing about what a title looks like, and it made
the compact band unfixable one template at a time: the numbers that overflow a
40dp slot were the same numbers in each of them.
Note: WidgetSlots.tall is used by CalendarWidget and ListWidget,
which declare a 180dp minResize in one axis and so cannot be handed a compact
slot through this plugin's own widget info XML. Their compact branches are
written and tested anyway, because a consumer writes their own.
Added: JVM layout tests. glance-appwidget-testing composes a template at a
chosen slot size and lets the test assert on the result, so what a widget shows
at 110x40 is now covered without an emulator. Robolectric is pinned to SDK 34 in
src/test/resources/robolectric.properties: it otherwise simulates the
compileSdk, 36, which it will only run on Java 21 while this module stays on 17.
Added: GlanceTheme.useDynamicColor paints the widget from the wallpaper
palette on Android 12 and above. Off by default.
Below API 31 the request is ignored and the theme's own colours are used. That
is deliberate: Glance's DynamicThemeColorProviders resolves through resources
whose values-v31 variant points at @android:color/system_accent1_* and
whose plain values variant is the static Material baseline, so passing them
straight through would not degrade to the app's colours on Android 8-11 -- it
would silently repaint the widget purple.
Changed: the four theme colours are now resolved once, in widgetColors,
instead of once per template. The same block appeared in all seven, and the
built-in defaults had drifted between them.
Fixed: widget taps could be lost. Every template handled its taps with a
Glance lambda action, which runs in the app's own process -- and the system is
free to start that process from cold just to deliver the tap, with no Flutter
engine in it. The event went to a null listener and vanished, with nothing in
the logs. Taps are now handled by ActionCallbacks that write to a queue on
disk first, so the Dart handler no longer has to be alive at the moment of the
tap; the backlog is capped at 100 and replays with the time the tap actually
happened.
Added: checkboxes in ListWidget toggle in place, via
actionRunCallback<ToggleListItemAction>. Ticking one no longer launches the
app. The stored state is flipped before the event is queued, so the box does
not spring back when the widget next redraws.
Fixed: BackgroundUpdateConfig was saved to SharedPreferences as Gson
JSON with no ProGuard rule protecting its field names. In a minified build R8
renamed them to a, b, c -- and the letters are not stable across builds,
so an app update made every previously saved config unreadable. load()
swallows the parse failure and returns null, so the symptom was background
updates silently stopping after an update, release builds only.
consumer-rules.pro now keeps the field names of both serialized models, and
CI builds a minified APK and asserts against the R8 mapping file that they
survived.
Fixed: GlanceTheme.borderRadius had no effect. Three of the seven
templates read the value into a local and never used it; the other four did not
read it at all, so every Android widget had square corners whatever the theme
said, while iOS rounded exactly as asked (#20). All seven now round by it.
Two notes on the fix. The radius is no longer put through Int on the way --
?.toInt() ?: 16 floored a 12.5dp radius to 12dp before discarding it. And
Glance's cornerRadius(Dp) is honoured on Android 12 (API 31) and above;
below that the platform offers no way to round a widget by a value chosen at
runtime, so those devices keep square corners. That is a platform limit, not a
default -- it is stated here rather than left to be discovered.
Breaking: matches glance_widget_platform_interface 2.0.0. Update methods
return Future<void> and throw GlanceWidgetException on failure instead of
returning Future<bool>. See the main package's
migration guide for details.
Added #
updateBatchapplies many widget updates, of any mix of templates, from one method call. Entries are applied in order and every one is attempted; the reply carries afailureslist naming the widgets that could not be updated, which Dart turns into aGlanceWidgetBatchException. A template this build does not know is that one widget's failure rather than the batch's, so a newer Dart side talking to an older plugin still gets its other widgets updated.
Fixed #
-
Gauge widgets ignored the metrics they were given. The radial gauge read
progress,value,gaugeColor,minLabelandmaxLabeloff the payload, none of whichGaugeWidgetDatahas ever sent, so every gauge drew an empty arc reading0%; the dashboard gauge forwarded metrics untouched, but the template readsvalueas text and needs aprogressfraction, so every card showed a blank value and no bar. Both shapes are now derived from the metrics, matching what the iOS templates draw. -
Method channel handlers replied
success(true)before knowing whether the update had been applied, so a rejected update was reported to Dart as a success. Handlers now await the real outcome and answer with an error when the platform refused. -
Two widgets built from the same template could not hold different data. Every update was written into every placed instance of that template, so updating
'btc'also overwrote a widget showing'eth'. Updates are now routed to the instance carrying the targetwidgetId, which is what the documented "unique identifier for this widget instance" always implied. A freshly placed instance is claimed by the first update that finds no other home, a lone instance is re-keyed rather than left unreachable, and an id that matches nothing among several placed widgets is refused withNO_WIDGET_INSTANCEinstead of overwriting one at random. -
imageUrldid nothing. It is documented, validated and sent over the channel, and no native code read it -- the widget drew a blank box with no error and no log. Image sources are now resolved when the update is applied, not when the widget is drawn: a Glance composition runs in the host's process on the host's schedule and is no place for network I/O. -
The image fit setting never applied. Dart sends
fit; this readdata["imageFit"], which was always null. -
Images were decoded inside the
@Composablebody, so the same picture was decoded again on every recomposition. Decoding now happens once at update time, and the result is held in a bounded cache. -
Images were decoded at full resolution. A 4000x3000 photo is roughly 48 MB of ARGB_8888, over the limit for handing to a widget host, and the resulting
OutOfMemoryErroris anErrorrather than anException-- so thecatch (e: Exception)around the decode never caught it and the host process died. Images are now downsampled to fit a 512 px budget before decoding, with the error caught as a backstop. -
A widget that stopped having an image kept its downsampled file on disk forever. The
imagePathpreference was removed, which is what makes the widget stop drawing the old picture, but nothing deleted the file it pointed at and nothing could reach it afterwards.ImageStore.evictexisted for this and was never called from anywhere. Dropping the file is now the store's own job rather than the caller's, so it cannot be forgotten again. -
Every
catch (e: Exception)in the widget manager swallowedCancellationException, which is anExceptionin Kotlin. A cancelled coroutine was reported to Dart as a rejected update, and structured concurrency broke for callers upstream. Cancellation is now rethrown at all 15 sites.
Security #
- Only
httpandhttpsimage URLs are fetched. A widget update carries an app-supplied string into a network stack, andfile://orcontent://would have made that a way to read arbitrary local data. Downloads are also capped at 16 MB and time out. - Redirects are followed by hand, at most five deep, with every hop put back
through the same scheme check.
HttpURLConnection.instanceFollowRedirectswould have applied that check to the first hop only, so a permittedhttps://address could have handed the fetch on to something else.
Private and loopback addresses are deliberately not blocked: the app already
holds the INTERNET permission and could make the same request itself, so
refusing them would buy little and would break serving widget images from a LAN
host or a local dev server. Apps that put third-party URLs into imageUrl
should validate them as they would any other URL they fetch.
Added #
- JVM unit tests for the plugin's Android sources, run in CI. The instance
routing rules live in
WidgetInstanceResolver, deliberately free of Android types so they can be tested without an emulator.
Changelog #
1.0.0 #
- Complete all platform method overrides (was 7/18, now 18/18)
- Method channel namespace changed from
com.example.glance_widgettodev.glance.widget - Added
dispose()override for resource cleanup - Updated dependency:
glance_widget_platform_interface: ^1.0.0
0.5.0 #
- New Widget Templates - Calendar, Image, Chart, Gauge
CalendarGlanceWidget- Date header with event listImageGlanceWidget- Base64 image display with content modesChartGlanceWidget- Canvas-rendered line/bar/sparkline chartsGaugeGlanceWidget- Radial gauge (Canvas) and dashboard metric cards
- Deep Link Support - All widgets support
deepLinkUriviaactionStartActivity - Lock Screen Widgets -
widgetCategory="home_screen|keyguard"for all templates - Interactive Actions - Checkbox toggle support in List Widget
- Widget Configuration -
completeWidgetConfigurationhandler - Updated Compose BOM to 2025.01.01, coroutines to 1.9.0, WorkManager to 2.10.0
- Updated SDK constraints to Dart >=3.6.0, Flutter >=3.27.0
- Updated dependency on glance_widget_platform_interface to ^0.6.0
0.3.0 #
- Background Updates - WorkManager integration for updates when app is closed
GlanceWidgetWorker- CoroutineWorker for API fetching and widget updatesBackgroundUpdateManager- WorkManager scheduling and cancellationBackgroundUpdateConfig- Configuration storage with SharedPreferences
- New method handlers in
GlanceWidgetPlugin:configureBackgroundUpdatecancelBackgroundUpdategetBackgroundUpdateStatus
- Added WorkManager dependency (
androidx.work:work-runtime-ktx:2.9.0) - Updated dependency on glance_widget_platform_interface to ^0.4.0
0.2.1 #
- JSON Serialization - Fixed list widget item parsing using proper JSON instead of delimiter-based parsing
- Error Handling - Added
UpdateResultsealed class for structured error reporting - Backward Compatibility - Legacy delimiter parsing preserved for existing widget data
- Glance 1.1.1 Compatibility - Fixed ColorProvider API for Compose Color
- Compose BOM - Added Compose BOM 2024.12.01 dependency for runtime compatibility
- CircularProgressIndicator - Changed to percentage display (Glance only supports indeterminate mode)
- isWidgetPushSupported - Added method stub (returns false, iOS-only feature)
- compileSdk - Updated to API 36
- Updated dependency on glance_widget_platform_interface to ^0.3.0
0.1.1 #
- Updated dependency on glance_widget_platform_interface to ^0.2.0
- Compatible with iOS implementation release
0.1.0 #
- Initial release
- Android implementation using Jetpack Glance
- Three widget templates: Simple, Progress, List
- Instant widget updates (< 1 second)
- Theme support with dark/light modes
- Widget action callbacks to Flutter
- JSON serialization for list items
- Proper coroutine lifecycle management
- ProGuard/R8 rules included