Wear 9 min read

Glance 1.3.0 for Wear OS: Health-Aware Widgets, Studio Previews, and a Required Annotation Change

Glance 1.3.0 has been iterating quietly — alpha01 in May, alpha02 on July 1 — but the feature set that's landed across those two drops is the most significant Wear OS update the library has shipped. Health data integration, a proper Android Studio preview composable, a new manager API for querying active widget instances, and interaction event support all arrived in the same cycle. There's also a breaking change you'll hit on upgrade that isn't loudly advertised in the changelog.

If you're building Wear OS apps with Glance, this post covers everything that changed and what you actually need to do about it. If you haven't looked at Glance for Wear yet, this is a reasonable moment to start — the API surface is stabilizing into something you can build against seriously.

New
HealthData APIs for heart rate, steps, and more in widgets
New
WearWidgetPreview composable for Android Studio design-time rendering
Breaking
@AssociateWithGlanceWearWidget now required on every GlanceWearWidgetService

Health Data Integration: The Feature Wear Developers Have Actually Wanted

The biggest addition in this cycle is the androidx.glance.wear.health package, which gives Wear widgets direct access to health sensor data. The primary type is HealthData — renamed from DataType in alpha02 — which exposes metrics like heart rate, step count, and calories burned as composable state that Glance can render and update automatically.

The API is designed to fail safely on hardware that doesn't support a given sensor. Before reading any metric, you check the corresponding is*Available variable:

@Composable
fun HealthWidget(healthData: HealthData) {
    Column {
        if (healthData.isHeartRateBpmAvailable) {
            Text(
                text = "${healthData.heartRateBpm} bpm",
                style = TextStyle(fontSize = 16.sp)
            )
        } else {
            Text(
                text = "Heart rate unavailable",
                style = TextStyle(fontSize = 14.sp)
            )
        }

        if (healthData.isStepsAvailable) {
            Text(text = "${healthData.steps} steps today")
        }
    }
}

This matters more than it might look. Wear OS runs on a wide range of hardware — from premium Galaxy Watch Ultra class devices down to budget watches with stripped sensor sets. If you just read heartRateBpm without checking isHeartRateBpmAvailable, you'll get a default or garbage value on devices where the sensor isn't present or hasn't been initialized yet. The availability guards make the fallback explicit at the call site rather than hidden behind a silent zero.

What health metrics are available

The HealthData type follows the same pattern for all exposed metrics. The release notes don't enumerate every available is*Available pairing yet — the APIs are marked experimental — but the pattern is consistent: every numeric metric has a corresponding availability flag. You can expect heart rate, step count, and calorie data at minimum; the full list will stabilize as the alpha progresses.

Note on the rename: if you're coming from any code written against alpha01 that references androidx.glance.wear.health.DataType, it's now HealthData. The package path is the same, just the class name changed. Update imports and you're done.

The Required Annotation Change That Will Break Your Build

Alpha02 introduces @AssociateWithGlanceWearWidget as a mandatory annotation on every GlanceWearWidgetService subclass. If you already have a service that extends GlanceWearWidgetService, you must add this annotation before upgrading to 1.3.0-alpha02 or later.

// Before (will fail at runtime in alpha02+)
class MyWearWidgetService : GlanceWearWidgetService() {
    override val widget = MyGlanceWearWidget()
}

// After (required)
@AssociateWithGlanceWearWidget(MyGlanceWearWidget::class)
class MyWearWidgetService : GlanceWearWidgetService() {
    override val widget = MyGlanceWearWidget()
}

The annotation takes the KClass of your GlanceWearWidget implementation. Its purpose is to let the framework reverse-look up the widget class from the service class without having to instantiate the service first — useful for things like GlanceWearWidgetManager's query APIs, which we'll get to next. The older implicit association via the widget override still exists for the actual rendering path, but the annotation is now also required.

This is a runtime failure, not a compile-time error if you forget the annotation. Your widget may appear to work during development on a device where the framework happens to warm up the service first, but fail in production under cold-start conditions. Don't skip the annotation and assume it'll be fine because nothing crashed immediately.

WearWidgetPreview: Finally, Wear Widgets in the Design Preview

The most useful developer experience change in this cycle is WearWidgetPreview — a new composable specifically for rendering Wear widget content at design time in Android Studio's preview panel. Before this, previewing a Glance Wear widget required deploying to an actual watch or emulator and triggering a widget update. That feedback loop is slow enough that most Wear widget iteration happens by feel rather than by sight.

@Preview(
    device = "id:wearos_small_round",
    showBackground = true,
    backgroundColor = 0xFF000000
)
@Composable
fun MyWearWidgetPreview() {
    WearWidgetPreview {
        MyGlanceWearWidget().Content()
    }
}

WearWidgetPreview wraps your widget's composable content and handles the round-screen clipping, density, and font scaling that a real Wear OS device applies. You get an accurate representation of what the widget will actually look like without having to leave the IDE. Combined with the Android Studio Wear OS device shapes in the preview target selector, this is now genuinely usable for iterating on layout and typography.

It also handles the LocalInspectionMode propagation correctly — the 1.3.0-alpha02 changelog specifically notes a bug fix for PendingIntentAction that defers PendingIntent access in inspection mode to avoid crashing in a context where pending intents can't be created. Your widget code can check LocalInspectionMode.current to gate any real hardware access behind a preview-safe stub.

GlanceWearWidgetManager: Knowing What's Actually Running on the Watch

A persistent gap in the old Glance Wear API was the inability to query which widget instances were active at any given moment. If you wanted to trigger a data refresh across all instances of a widget, you had no reliable way to enumerate them without side-channel state you maintained yourself.

GlanceWearWidgetManager fills that gap. It's the canonical way to get the list of active widget IDs for a given widget class, which you can then use with the updated triggerUpdate(id) and new triggerUpdateAll() APIs:

class HealthDataRefreshWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val manager = GlanceWearWidgetManager(applicationContext)
        val activeIds = manager.getActiveWidgetIds(MyGlanceWearWidget::class)

        activeIds.forEach { id ->
            MyGlanceWearWidget().update(applicationContext, id)
        }

        return Result.success()
    }
}

Or, if you want to update everything at once without enumerating IDs:

// Trigger a recompose on all active instances of this widget class
GlanceWearWidget.triggerUpdateAll(applicationContext, MyGlanceWearWidget::class)

The practical use case is background data refresh. When your WorkManager job fetches new health data or a notification arrives that should change widget content, you now have a clean path to push that update to every active widget instance without maintaining your own instance registry. For Wear OS apps where widgets are often the primary UI surface — a watch face complication showing the current session's step count, for example — this changes what's practical to build.

Note on IDs: the update API now takes a WidgetInstanceId type rather than a raw identifier. This was introduced in alpha01. If you have code calling the old update API with a raw value, you'll need to update it. The manager's query APIs return WidgetInstanceId instances, so the type flows naturally from query through to update.

WearWidgetBrush: Image Backgrounds and Gradient Fills

The alpha02 release added an image brush type to WearWidgetBrush, joining the vertical and horizontal gradient types that landed in alpha01. Combined, you can now set a Wear widget background to:

GlanceWearWidget(
    content = { HealthWidget(healthData) },
    params = WearWidgetParams(
        brush = WearWidgetBrush.image(myBitmap),
        containerType = CONTAINER_TYPE_TILE_COMPAT
    )
)

The image brush makes it practical to build watch face-style widgets where the background is a rendered illustration or a dynamic chart rather than a flat color. For fitness or health apps, a circular progress arc rendered as a bitmap background with a numeric readout composable on top is now a reasonable design — and Glance handles the background caching, not your code.

The container type rename you need to know

While on the topic of WearWidgetParams: the constant CONTAINER_TYPE_FULLSCREEN was renamed to CONTAINER_TYPE_TILE_COMPAT in alpha01. If you're upgrading from pre-1.3.0, grep your codebase for the old name — the compiler won't tell you it's gone until you build.

Interaction Events: Wear Widgets That React to Gestures

Wear OS widgets have historically been read-only surfaces — tap to launch, but no in-widget interaction beyond that. Glance 1.3.0 adds Interaction Events to GlanceWearWidget, which opens the door to gestures and state changes within the widget surface itself.

The release notes mark this as a new capability on GlanceWearWidget without fully specifying the gesture vocabulary yet — this is alpha-track behavior that will likely expand before stabilization. The underlying infrastructure is there: the framework can now deliver interaction events to your widget and trigger a recompose in response, rather than only updating on background data pushes.

The practical implication is widgets that can toggle state — mark a workout complete, pause a timer, skip a song — without launching the full app. That's the kind of experience that makes Wear OS feel fast rather than just a notification surface.

Remote Compose Improvements

Remote Compose is the substrate that lets Glance widgets be rendered by a remote host process rather than your app process — used for Wear OS tiles and the health-aware widgets covered above. Two additions in 1.3.0 are worth knowing about if you're building anything on top of Remote Compose directly:

captureRemoteDocument() is now a public Flow API, letting you observe the captured widget state as a Kotlin Flow rather than a one-shot snapshot. This is mainly useful for tooling and testing scenarios where you want to observe how widget content changes over time.

RemoteInt compare operators are now exposed, which sounds minor but matters when you're doing conditional rendering inside a Remote Compose tree: you can now write comparisons like remoteSteps > remoteGoal without boxing to platform types.

What This Looks Like in Practice: A Health Widget for Samachar

To make this concrete, consider a reading time tracker widget for a news app — not a health app, but the same pattern applies. You want a Wear widget that shows today's reading streak and updates when the user completes an article. With the full 1.3.0 feature set, the architecture is clean:

  1. GlanceWearWidget renders the streak count using a composable that reads from DataStore.
  2. @AssociateWithGlanceWearWidget on the service wires the class association.
  3. When an article is marked read in the phone app, a WorkManager job calls triggerUpdateAll() — no instance ID management needed.
  4. WearWidgetPreview lets you iterate on the circular layout without deploying to a watch every time.
  5. A gradient WearWidgetBrush gives the widget visual depth that flat colors can't match.

The pieces that were missing before — the update trigger, the preview, the brush variety — were real friction points that pushed Wear widget development into the "only if we have time" bucket. 1.3.0 removes most of that friction.

Should You Upgrade Now?

If you're already shipping a Glance Wear widget, the minimum change is adding @AssociateWithGlanceWearWidget before you upgrade to alpha02. Do that first, verify nothing breaks, then adopt the new APIs incrementally. The health data and manager APIs are experimental but stable enough to build against in a feature branch — just don't ship them as the load-bearing core of a production widget without hedging on the API shape potentially changing before stable.

If you haven't started Wear widget development yet, 1.3.0 is the right moment to start. The API surface is coherent, the tooling story with WearWidgetPreview is usable, and health data integration is available in a way that wasn't there six months ago. Wear OS's market presence has grown steadily, and widgets are increasingly the primary surface users interact with — not the secondary "nice to have" they were at Wear OS 2.0 launch.

The stable release is still a few alpha cycles away, but the direction is clear and the breaking changes so far have been renames rather than architectural pivots. Start building now, keep an eye on the alpha changelog, and you'll be in good shape when 1.3.0 hits stable.

Comments 0

No comments yet. Be the first to leave one!

Leave a comment