Documentation

Widgets

Home screen widgets are .wh files in src/widgets/. They compile to Glance, and there is no XML to write.

A widget

The + prefix is required — it is what marks the file as a widget rather than a component:

// src/widgets/+Clock.wh
<Column fillMaxSize background="#000" align="center">
  <Text fontSize={32} color="#FFF">{currentTime()}</Text>
</Column>

That is a complete widget. Whitehall generates the GlanceAppWidget, its GlanceAppWidgetReceiver, and the manifest entry.

Configuring it

Wrap the markup in <Widget> to say how it should appear in the picker and how it may be sized:

// src/widgets/+Clock.wh
<Widget size="2x2" desc="Shows current time" update="15m">
  <Column fillMaxSize background="#000" align="center">
    <Text fontSize={32} color="#FFF">{currentTime()}</Text>
  </Column>
</Widget>
PropMeaning
sizeHome screen cells, as "columns x rows""2x2", "4x1"
descThe description shown in the widget picker
updateHow often the system refreshes it — "15m", "1h"
resizableWhether the user may resize it after placing
minWidth / minHeightExplicit dp, if cells are not the right unit
size is the one to use. It works out minimum dimensions with Android's own formula — (cells × 70) − 13 — and also sets targetCellWidth/targetCellHeight, which is how Android 12 and later size widgets. Setting minWidth by hand gets you the first half and not the second.

Interaction

Two things a widget can do when tapped.

Open the app somewhere

<Button text="Open" onClick={() => $navigate("/detail/42")} />

$navigate from a widget launches the app onto that route.

Refresh in place

var count: Int = 0

<Column fillMaxSize background="#0F172A" p={12}>
  <Text fontSize={24} color="#FFF">{count}</Text>
  <Button text="Refresh" onClick={() => $refresh()} />
</Column>

$refresh() re-runs the widget without opening the app — for a counter, a refresh button, or anything the user should be able to update from the home screen.

What you can put in one

Glance is not Compose, and it supports a much smaller set of components. Whitehall's widget vocabulary is what Glance can actually render:

Box, Row, Column, Text, Button, Image, LazyColumn, Spacer, CheckBox, Switch

The rest of the component library is not available in a widget, and neither are transitions or morph drivers — the home screen redraws widgets as static images rather than running your composition.

Keeping one up to date

update is a hint the system honors loosely and never faster than about fifteen minutes. For anything you actually need to happen, drive it from a worker instead:

$periodic(api.refreshFeed, every: { mins: 60 }, constraints: ["network"])

See Also