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> | Prop | Meaning |
|---|---|
size | Home screen cells, as "columns x rows" — "2x2", "4x1" |
desc | The description shown in the widget picker |
update | How often the system refreshes it — "15m", "1h" |
resizable | Whether the user may resize it after placing |
minWidth / minHeight | Explicit 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
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
- $dispatch / $periodic — background work that can feed a widget
- $navigate — routing from a widget into the app
- Project Structure — where widget files live