Skip to content
All posts
August 1, 20264 min read

Building an Android Widget With Jetpack Glance

Jetpack Glance lets you build home-screen widgets with a Compose-like API instead of clunky RemoteViews. Here's how it works, the constraints that make widgets different from app UI, and how to keep them updated efficiently.

Jetpack GlanceWidgetsAndroidJetpack ComposeKotlin
Share:

Home-screen widgets used to mean

code
RemoteViews
— a constrained, XML-driven API that felt like building UI with one hand tied behind your back. Jetpack Glance changes that. It gives you a Compose-like declarative API for widgets, so building one feels familiar if you know Compose. It's not quite the same as app UI, though, and understanding the differences is what keeps a widget from misbehaving.

Glance Looks Like Compose, But Isn't

You write a widget by extending

code
GlanceAppWidget
and describing its content with Glance composables. They're named like their Compose cousins —
code
Column
,
code
Row
,
code
Text
,
code
Button
— but they come from the Glance package and compile down to
code
RemoteViews
under the hood.

kotlin
class StatsWidget : GlanceAppWidget() {
    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            Column {
                Text("Today")
                Text("8 glasses")
            }
        }
    }
}

The mental model transfers, but the component set is smaller and the styling system is its own thing, because everything must ultimately render as

code
RemoteViews
that the launcher process can host. So you get the declarative ergonomics without the full Compose toolbox.

Widgets Live in Another Process

The constraint that catches people is that a widget isn't running inside your app — it's hosted by the launcher. You can't just read your in-memory state; the widget has to fetch its data fresh. Glance gives each widget its own state storage, and for app data you read from your persistence layer (DataStore, Room) when providing content. Thinking of a widget as a tiny separate surface that pulls from your data source, rather than a window into your running app, avoids a lot of confusion.

Update Deliberately, Not Constantly

Widgets can't update on every state change like app UI, and you wouldn't want them to — frequent updates drain battery and the system rate-limits them anyway. Instead you update on meaningful events: data changed, a periodic refresh, a user action. You trigger an update by telling Glance to re-provide content for the widget.

kotlin
StatsWidget().update(context, glanceId)

The discipline is updating when the data genuinely changed, ideally driven by your data layer, rather than polling on a tight timer. A widget that updates a few times a day when its numbers change is both correct and battery-friendly; one that refreshes every minute is neither.

Handle Interaction Through Actions

Widgets can be interactive, but not by calling functions directly — taps are routed through actions that the system delivers, since the widget runs out-of-process. Glance provides action helpers to launch your app, run a callback, or update the widget. A tap might open the relevant screen in your app, or run a lightweight callback to toggle something and refresh. Keeping interactions simple matters: a widget is a glanceable surface, not a place to build a whole flow, and overloading it with controls works against what widgets are for.

Keep It Small and Glanceable

The design principle that makes a good widget is restraint. The whole point is information at a glance — the one or two things the user wants to see without opening the app. Cramming a dense UI into a widget fights both the form factor and the platform's constraints. I pick the single most valuable piece of information, present it cleanly, and let a tap take the user into the app for anything more. A focused widget that shows exactly the right number is more useful, and more likely to earn a spot on someone's home screen, than a busy one trying to be a mini-app.

If there's a single reframe that makes Glance widgets click, it's thinking of a widget as a published view of your data rather than a piece of your running app. Your app is the editor; the widget is the read-mostly billboard the launcher renders from your data source. Once you hold that model, the constraints stop feeling arbitrary: of course it can't read your in-memory state, of course it updates on data changes rather than continuously, of course interactions are routed rather than direct — it lives in another process, looking at your data from the outside. Design within that reality instead of fighting it, keep the widget focused on the one thing worth glancing at, and you end up with something genuinely useful that quietly earns its place on the home screen.

Key Takeaways

  • Glance gives widgets a Compose-like declarative API, but with a smaller component set that compiles to
    code
    RemoteViews
    .
  • A widget runs in the launcher process, not your app, so it must fetch data fresh from your persistence layer.
  • Update on meaningful data changes or periodic refreshes, not a tight timer — frequent updates drain battery and get rate-limited.
  • Handle taps through Glance actions, since the widget runs out-of-process; keep interactions simple.
  • Design for glanceability — show the one or two key things and route deeper interaction into the app.
Share:
S

Sudarshan Chaudhari

AI Systems Builder / Product Engineer

Bangkok, Thailand

Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.

Stay updated

Get new posts on Android, Kotlin, and solo dev straight to your inbox.

Newsletter preferences

Related Apps

MyFamilyTracker

Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.

Building something? Available for Android dev and QA consulting.

Work with me

Comments — powered by Giscus

Apps tagged with this