Skip to content

Building a widget using Jetpack Glance

App widgets are miniature application views that can be embedded in the home screen of your device. Jetpack Glance is a library built on top of Jetpack Compose that allows you to build these widgets for Android.

In the above image you can see a weather widget.

In this other example, you can see a music app widget that allows you to control the music playback.

In this tutorial we’ll create a quotes widget.

Setup

Jetpack Glance was released in 2021 and as of now, it has a release candidate.

Before we start make sure you have Jetpack Compose enabled.


android {
    buildFeatures {
        compose true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.1.0-beta03"
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Then include the dependencies for Glance.

dependencies {
    implementation "androidx.glance:glance:1.0.0-rc01"
    implementation "androidx.glance:glance-appwidget:1.0.0-rc01"
}

The first thing we need to do is to define an app widget provider. Create a file named app_widget_provider.xml inside res/xml.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="100dp"
    android:minHeight="100dp" />

There are many attributes you can define, if you want to learn more about them you can read this documentation.

After that we need to create our widget.

class QuotesWidget : GlanceAppWidget() {

    override val stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            Text(text = "Hello World!")
        }
    }

}

The main thing here is the provideGlance function, that’s where you define which composable you want displayed.

Make sure you import the composables from Glance. For example you need to use androidx.glance.text.Text instead of androidx.compose.material3.Text.

After that we need to declare our receiver.

class QuotesWidgetReceiver : GlanceAppWidgetReceiver() {

    override val glanceAppWidget: GlanceAppWidget = QuotesWidget()

}

Now go to your AndroidManifest and import the receiver you just declared.

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>

    <application ...>
        <receiver
            android:name=".QuotesWidgetReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/app_widget_provider" />
        </receiver>        
    </application>

</manifest>

If you don’t have an activity you need to modify Android Studio so it runs without one. First go to your run configuration.

Then select “Always install with package manager” and change “Launch:” to “Nothing”.

After that you should be able to run the app. Open the widgets screen and find your widget there.

Now that we already have a widget, let’s add some functionality to it.

private val quotes = listOf(
    "Be yourself; everyone else is already taken. ― Oscar Wilde",
    "A room without books is like a body without a soul. ― Marcus Tullius Cicero",
    "You only live once, but if you do it right, once is enough. ― Mae West",
)

private val currentQuoteKey = stringPreferencesKey("currentQuote")

class QuotesWidget : GlanceAppWidget() {

    override val stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition

    override suspend fun provideGlance(context: Context, id: GlanceId) {
        provideContent {
            val preferences = currentState<Preferences>()
            val currentQuote = preferences[currentQuoteKey] ?: quotes.random()

            MaterialTheme {
                Box(
                    modifier = GlanceModifier
                        .background(Color.White)
                        .padding(16.dp)
                        .clickable(actionRunCallback<RefreshQuoteAction>())
                ) {
                    Text(text = currentQuote)
                }
            }
        }
    }

}

class RefreshQuoteAction : ActionCallback {

    override suspend fun onAction(
        context: Context,
        glanceId: GlanceId,
        parameters: ActionParameters
    ) {
        updateAppWidgetState(context, PreferencesGlanceStateDefinition, glanceId) { preferences ->
            preferences.toMutablePreferences().apply {
                this[currentQuoteKey] = quotes.random()
            }
        }
        QuotesWidget().update(context, glanceId)
    }
}

Run the widget again and you’ll see a quote. Tap the widget and it’ll display a different quote.

If you want to learn more about widgets you can follow the Android documentation about them.

Photo by Vincent LaVigna on Unsplash