glance_widget 2.0.0
glance_widget: ^2.0.0 copied to clipboard
Create instant-updating home screen widgets for Android (Jetpack Glance) and iOS (WidgetKit). Supports Simple, Progress, List, Image, Chart, Calendar, and Gauge widget templates.
2.0.0 #
Added: getWidgetData(widgetId) reads back what a widget is currently
showing, as the sealed WidgetData that was sent, wrapped in a
GlanceWidgetSnapshot carrying the time of the write and the theme sent with
it. Answers null when there is no record, and throws
GlanceWidgetFormatException -- a new type -- when there is a record this
version cannot read, because "nothing there" and "something I do not
understand" are different answers and only the first means the widget is safe
to overwrite unseen. Both platforms store the payload exactly as it arrived and
hand it back as text; the mapping between fields and classes stays in Dart
rather than being written a second time in Kotlin and a third in Swift. The
record is dropped wherever the id is. See #37.
Added: Live Activities. startLiveActivity, updateLiveActivity,
endLiveActivity, isLiveActivityRunning and areLiveActivitiesEnabled put a
card on the iOS Lock Screen and in the Dynamic Island for something in
progress. Content is one fixed shape, LiveActivityContent, because a widget
extension has to name a concrete ActivityAttributes type at compile time and
ActivityKit matches an activity to its presentation by that type.
The three mutating calls throw UnsupportedError on Android rather than doing
nothing: Android 16's Live Updates are a notification, not a widget, and a
silent no-op would leave a caller believing something was on screen. The two
queries answer false there instead, so they are safe to branch on anywhere.
Fixed: removing a widget from the home screen left everything behind --
its payload, its downsampled image on disk, and its id in
getActiveWidgetIds() forever. Neither platform had a deletion hook at all.
Android now cleans up from onDelete, and an id whose second copy is still
placed is kept.
Added: GlanceWidget.forgetWidget(id) drops the payload, the image and the
id on demand. It is the only way an id is ever dropped on iOS, where WidgetKit
tells an extension nothing about its widget being removed and the configuration
intents that would identify one are decodable only by the app that defined
them.
Changed: getActiveWidgetIds() is sorted and documented. Both platforms
returned a Set directly, so two calls could answer in different orders and a
caller rendering the list got it reshuffled at random. The doc said "currently
displayed on the home screen"; what it returned was every id the app had ever
written. Neither platform can answer the first question, so the contract is now
the one it can keep: the ids there is data stored for.
Fixed: GlancePreview drew Android corners at a hardcoded 16dp, so a
widget themed with borderRadius: 4 rendered with 4dp corners on a device and
16dp ones in the preview. That reading was correct when it was written -- the
Android templates read the value and never used it -- and stopped being correct
when they started applying it. The preview now draws what the plugin applies.
Added: GlancePreview.androidApiLevel picks the Android version to
imitate, defaulting to 31. Glance refuses cornerRadius below Android 12, and
rounded widget corners are an Android 12 feature at all, so a widget is square
on the 8.0-11 devices this plugin still supports. Pass 30 to see that.
Added: dart run glance_widget:doctor checks a project's native setup and
names what will stop a widget working. Almost every way to get this wrong fails
quietly -- a receiver with exported="false" installs and never reaches the
picker, a missing GlanceWidgetAppGroup leaves every update silently dropped,
two Info.plists naming different groups give you two working stores that are
not the same store. It reads files rather than building, so it needs neither
Xcode nor the Android SDK, and it exits 1 only on findings that would actually
break something, which makes it usable as a CI step. Where it cannot tell, it
says so instead of guessing: a platform it could not inspect is reported
separately and never counted as a pass.
Fixed: on iOS, widget updates silently did nothing unless your App Group
happened to be the example's. Both targets now read the group from the
GlanceWidgetAppGroup key in their Info.plist -- see the updated App Groups
step in the README, which previously told you to configure entitlements and
stopped there.
Fixed: on Android, all seven templates painted one layout at every slot size, so a widget dragged smaller cut its own content off the bottom -- the percentage inside a progress dial, every gauge metric below the first, all but 1dp of a calendar's events. Each template now measures its slot and drops what restates something already on screen before it drops the thing the widget exists to show. See "What a small widget shows" in the README for the order. The iOS templates already did this.
Added: GlanceTheme.useDynamicColor opts a widget into Material You on
Android 12 and above. Off by default; your theme's colours remain the fallback
everywhere else, iOS included. See the README for why this is gated rather than
handed to the platform.
Fixed: GlanceTheme.borderRadius did nothing on Android -- every widget had
square corners however the theme was configured, while iOS rounded as asked
(#20). Rounded corners need Android 12 or newer; see the platform comparison in
the README.
Added: checkboxes are interactive in place on both platforms. Ticking one in
ListWidget no longer launches the app; the action reaches your existing
onAction listener unchanged the next time the app runs. See the README for the
two consequences -- the widget's own copy of the state is flipped first, and the
backlog is capped.
Fixed: on Android, widget taps could be lost outright. They were delivered to a process the system may have started from cold with no Flutter engine in it, so the event went nowhere and left no trace. Taps are now queued to disk before Dart is involved.
Fixed: on Android, background updates stopped working after an app update in release builds. The saved configuration's field names were being renamed by R8, which made the stored JSON unreadable by the next version -- and the failure was silent.
Fixed: on iOS, a list widget with a deepLinkUri set never delivered
checkboxToggle at all.
Added: iOS lock screen and Smart Stack widgets. Six of the seven templates
now render in the .accessoryCircular, .accessoryRectangular and
.accessoryInline families. No API change: the same updateSimpleWidget call
feeds a home screen widget and a lock screen one. See the README for what the
lock screen changes -- theme colours are ignored there, and ImageWidget does
not offer the families at all.
Breaking: widget updates now report failures instead of hiding them. See doc/migration_v2.md for the full migration.
Breaking: iOS now requires a deployment target of 17.0, up from 16.0. The
iOS widget templates need AppIntentConfiguration, which is the only widget
configuration carrying a per-instance parameter and therefore the only way a
placed widget can know which widgetId it shows.
Added #
-
GlancePreviewdraws a widget inside the app, so a change to the data can be seen on hot reload instead of through a build, an install and a trip to the home screen. It renders per platform rather than averaging the two: passplatform:to see the other host, or put both side by side. The differences it shows are real ones -- the Android simple template dropsiconName, its radial gauge draws only the first metric, its charts are stretched bitmaps, and it ignoresGlanceTheme.borderRadiusbecause the launcher clips. -
GlanceWidget.batchupdates many widgets, of any mix of templates, in a single platform call. The per-template helpers each cross the method channel once, so refreshing a dashboard of twenty widgets cost twenty round trips and serialised the shared theme twenty times over. Measured on the same twenty updates:roundTrips=20 payloadBytes=5700becameroundTrips=1 payloadBytes=2974.Every update is attempted rather than stopping at the first failure -- one widget missing from the home screen is no reason to leave the other nineteen showing stale data. The ones that succeeded stay applied and a
GlanceWidgetBatchExceptionnames the rest, oneGlanceWidgetBatchFailureper widget. A malformed update is refused before anything is sent, so a mistake in the calling code cannot half-apply.
Fixed #
widgetIddid not actually identify an instance on either platform. On Android every update was written into every placed widget of that template, so updating'btc'overwrote a widget showing'eth'. On iOS the mirror image: writes were per id, reads were not, so every placed widget rendered whichever id was updated last. Android now routes each update to the instance holding the id; on iOS the person placing the widget picks the id from the ones the app has sent data for (long-press → Edit Widget).imageUrlwas documented, validated and sent over the channel, and no native code on either platform read it. Images are now fetched and downsampled when the update is applied rather than when the widget is drawn — a widget host composes on its own schedule and under a much tighter memory budget, and a full-resolution photo was enough to kill the process on Android.- A failed widget update was reported as a success. The native plugins answer a
failed update with a platform error, never with
false, so theFuture<bool>every method returned wastrueon success andtrueagain for any caller who did not inspect it after the Dart layer had swallowed the error.DebouncedWidgetControllercounted those failures as completed updates, soupdateCount,timeSinceLastUpdateandisStaleall lied. DebouncedWidgetControllerfailures triggered by the debounce timer, the max wait timer, or the app going to the background reached nobody at all.- Concurrent debounced dispatches could land out of order; they are now serialised.
GlanceWidgetControllerbypassed the platform guard, soupdate,setThemeandonActionthrewMissingPluginExceptionon Web and desktop instead of no-opping like the rest of the API.GlanceConfig.strictMode = truerejected valid JSONPath expressions such as$..authorand$.book[?(@.price < 10)], which the native parser accepts.ChartWidgetDataandGaugeWidgetDatacould not beconst: aList.lengthassert in aconstconstructor is not const-evaluable, and the resulting error pointed at the call site rather than the constructor. Both are const again, and the emptiness check moved toWidgetData.validate()at the channel boundary, where it also runs in release builds.
Changed #
- All update, theme, configuration and background methods return
Future<void>and throwGlanceWidgetException— carrying the platform'scode,messageand the originalPlatformException— instead of returningFuture<bool>. getWidgetPushToken,getActiveWidgetIdsandgetBackgroundUpdateStatusthrow on a platform failure rather than answeringnull,[]or{isConfigured: false}.isWidgetPushSupportedstill returnsbool: it asks a real question about the platform.- Unsupported platforms are always a silent no-op, never an exception.
GlanceWidget.onActionyields an empty stream there. - JSONPath validation rejects only what no parser accepts (empty, or not
starting with
$); unrecognised expressions are logged and passed through.
Added #
GlanceWidget.isSupported— ask whether the platform has home screen widgets instead of configuring what failure should look like.DebouncedWidgetController.errors,.failedCountand.lastError.
Removed #
MethodChannelGlanceWidget.throwOnError— failures always throw.GlanceConfig.strictMode— it conflated platform support with JSONPath strictness; both are now explicit.
1.0.1 #
- Updated sub-package dependencies to v1.0.0 (platform_interface, android, ios)
1.0.0 #
Breaking Changes #
WidgetDatais now asealed class— all 7 data types extend it withtemplategetterGlanceWidgetControlleris now generic:GlanceWidgetController<T extends WidgetData>- Old controller methods (
updateSimple(),updateProgress(), etc.) replaced by single type-safeupdate(T data) DebouncedWidgetControlleris now generic:DebouncedWidgetController<T extends WidgetData>- Background update methods moved from
GlanceWidgettoGlanceBackgroundclass - Method channel namespace changed from
com.example.glance_widgettodev.glance.widget
New Features #
- Compile-time type safety via generic controllers — wrong data type is a compilation error
- 7 convenience controllers:
SimpleWidgetController,ProgressWidgetController,ListWidgetController,ImageWidgetController,ChartWidgetController,CalendarWidgetController,GaugeWidgetController GlanceConfig.strictModefor configurable platform safetyPlatformGuardfor graceful behavior on unsupported platforms (Web, macOS, Windows, Linux)JsonPathValidatorfor background update JSONPath validationAppLifecycleListenerintegration inDebouncedWidgetController— flushes pending data on app background- Configurable
stalenessThresholdinDebouncedWidgetController - Platform interface
dispose()for resource cleanup on platform swap - Exhaustive
switchonWidgetData— compiler warns when new widget type added
Fixes #
- Constructor side-effects removed from controllers (lazy initialization)
- Stream multiplexing: single native EventChannel subscription regardless of controller count
- Resource cleanup on platform swap (no more stream leaks)
- Complete Android/iOS platform method overrides (was 7/18 and 9/18, now 18/18)
skippedCountinDebouncedWidgetControllernow correctly only counts replaced pending updates
Migration Guide #
// BEFORE (v0.7.0)
final ctrl = GlanceWidgetController(widgetId: 'btc', template: GlanceTemplate.simple);
await ctrl.updateSimple(SimpleWidgetData(title: 'BTC', value: '\$94k'));
// AFTER (v1.0.0)
final ctrl = SimpleWidgetController(widgetId: 'btc');
await ctrl.update(SimpleWidgetData(title: 'BTC', value: '\$94k'));
// BEFORE
await GlanceWidget.configureBackgroundUpdate(widgetId: 'crypto', ...);
// AFTER
await GlanceBackground.configureUpdate(widgetId: 'crypto', ...);
0.7.0 #
New Widget Templates #
- Image Widget - Display photos with title and subtitle (base64 + URL support)
- Chart Widget - Line, bar, and sparkline chart visualization
- Calendar Widget - Date header with event list and colored indicators
- Gauge Widget - Radial and dashboard metric displays
Platform Features #
- Deep Link Support - All 7 widget templates support custom deep link URIs
- Android Lock Screen Widgets - All widgets now support
keyguardcategory for lock screen placement - iOS Timeline Refresh - Configurable
.after(date)timeline policy for periodic widget refreshconfigureTimelineRefresh()/cancelTimelineRefresh()API
- Interactive Widget Actions - Checkbox toggle and item tap actions for List Widget
- New action types:
toggle,checkboxToggle,itemTap,configure - New action fields:
itemId,value,itemIndex
- New action types:
- Widget Configuration -
completeWidgetConfiguration()for handling widget setup flow
SDK Updates #
- Flutter 3.27+ / Dart 3.6+ minimum SDK requirement
- Compose BOM 2025.01.01, kotlinx-coroutines 1.9.0, WorkManager 2.10.0
- Updated
GlanceTemplateenum with 4 new values:image,chart,calendar,gauge
Testing #
- 167 unit tests (from 131) covering all new templates and features
- 40 platform interface tests
0.4.0 #
Background Updates (Android) #
- WorkManager Integration - Widget updates even when app is closed
- Periodic background updates with configurable interval (minimum 15 minutes)
- API fetching with custom headers support
- JSONPath-like expressions for value extraction
- Network-aware scheduling (only updates when connected)
- New API methods:
configureBackgroundUpdate()- Set up automatic updates from an APIcancelBackgroundUpdate()- Stop background updates for a widgetgetBackgroundUpdateStatus()- Check update configuration and state
New Types #
- GlanceTemplate enum - Widget template types (simple, progress, list)
Usage Example #
await GlanceWidget.configureBackgroundUpdate(
widgetId: 'crypto_btc',
template: GlanceTemplate.simple,
apiUrl: 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd',
interval: Duration(minutes: 15),
title: 'Bitcoin',
valuePath: r'$.bitcoin.usd',
valuePrefix: r'$',
);
0.3.1 #
Stability & Quality #
- JSON Serialization - Fixed list widget item parsing using proper JSON instead of delimiter-based parsing (
|||,::) - Error Handling - Added structured error types for both Android (
UpdateResult) and iOS (GlanceResult) - Input Validation - Added assert-based validation for widget data (empty titles, progress bounds, maxItems range)
- Backward Compatibility - Legacy delimiter parsing preserved for existing widget data
Android Compatibility (Glance 1.1.1) #
- Compose BOM - Added Compose BOM 2024.12.01 for runtime compatibility
- ColorProvider API - Fixed for Compose Color type
- CircularProgressIndicator - Changed to percentage display (Glance only supports indeterminate mode)
- compileSdk - Updated to API 36
Real-time Data Optimization #
- DebouncedWidgetController - New controller for high-frequency updates (crypto, stocks, live scores)
- Configurable
debounceIntervalandmaxWaitTime - Automatic coalescing of rapid updates
- Update statistics tracking (
updateCount,skippedCount) - Staleness detection (
isStale,timeSinceLastUpdate)
- Configurable
Developer Experience #
- Improved error messages for App Group configuration issues on iOS
- Better logging for widget update failures
- Documentation improvements
0.2.0 #
- iOS Support - Added WidgetKit implementation for iOS 16+
- Instant updates when app is in foreground (no budget limit!)
- Widget Push Updates support for iOS 26+ (server-triggered updates via APNs)
- Added
getWidgetPushToken()for server-side widget updates - Added
isWidgetPushSupported()for runtime platform check - Privacy manifest included for App Store compliance
- Updated description to reflect cross-platform support
0.1.0 #
- Initial release
- SimpleWidget - Title + Value + Subtitle template for prices, stats, metrics
- ProgressWidget - Circular and linear progress indicators
- ListWidget - Scrollable list with optional checkboxes
- Theme support (light/dark) with full customization
- Widget tap action handling via streams
- Instant updates (< 1 second) using Jetpack Glance
- Controller API for advanced widget management