Build Android App Widgets With Jetpack Glance

A modern approach to building android app widgets.

sridhar subramaniam
Better Programming

--

Before diving into the topic, let’s quickly take a history lesson to see why this Glance is an important and long-awaited feature.

  • Android app widgets were released as part of Android 1.5 (Cupcake).
  • Code inception in 2009.
  • In 2012 support added to display widgets on the lock screen, and provided third party apps to act as widget host (custom launcher application) and etc.
  • Over the period of time android system grew and received a lot of updates on the other part of the system but got very few updates over the widget framework.

Android 12 introduces Glance framework to create app widgets using jetpack-compose (declarative style) and much more additional updates to the widget framework itself.

What’s new in Android 12

Jetpack Glance for app widgets

  • Declarative API to build app widget UI.
  • Stateful widgets.
  • New clean api to handle user interaction.
  • Custom error UI (from XML).

Other updates on the widget framework

  • Scalable widget preview
  • Better theming support
  • New compound buttons
  • New API’s added to allow runtime modification of RemoteViews

Jetpack Glance for app widgets

What is Jetpack Glance

  • Jetpack Glance is a new framework built on top of the Jetpack Compose runtime.
  • Glance offers similar modern declarative Kotlin APIs that come with Jetpack Compose which helps to build responsive app widgets.
  • As it builds on top of Jetpack Compose runtime it’s not directly interoperable with other existing Jetpack Compose UI elements but interoperable with existing RemoteViews.
  • Glance provides a base set of composables to help build “glanceable” experiences.
  • Using the Jetpack Compose runtime Glance can translate composables into actual RemoteViews and display them in an app widget.
  • Glance provides a more intuitive API to handle user interactions. It abstracts away the complexities we would encounter while using RemoteViews and PendingIntent. It provides the following predefined actions for handling user interactions.
    1) actionRunCallback
    2) actionStartActivity
    3) actionStartService
    4) actionSendBroadcast
  • Inbuilt support for different size UI by defining SizeMode.Single, SizeMode.Exact or SizeMode.Responsive.
  • State management using GlanceStateDefinition.
  • LocalContext, LocalState, LocalGlanceId, LocalSize. LocalAppWidgetOptions are provided to the content() method using CompositionLocalProvider.

Even though the Glance framework seems like a fundamental change to the app widget. The creation of the app widget is pretty much the same and the only change is in how we represent the UI and maintain the state.

Create GlanceAppWidget

Step 0: Add dependency


dependencies {
// For AppWidgets support
implementation "androidx.glance:glance-appwidget:1.0.0-alpha04"

// For Wear-Tiles support
implementation "androidx.glance:glance-wear-tiles:1.0.0-alpha04"
}

android {
buildFeatures {
compose true
}

composeOptions {
kotlinCompilerExtensionVersion = "1.1.0-beta03"
}

kotlinOptions {
jvmTarget = "1.8"
}
}

Step 1: Create GlanceAppWidget

package com.gandiva.glance.resizeable.widget

import androidx.compose.runtime.Composable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.text.Text

class SimpleGlanceAppWidget : GlanceAppWidget() {

@Composable
override fun Content() {
Text(text = "Hello!")
}
}

Step 2: Attach with GlanceAppWidgetReceiver

package com.gandiva.glance.resizeable.widget

import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver

class SimpleGlanceAppWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = SimpleGlanceAppWidget()
}

Step 3: Add widget meta-data

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_widget_description"
android:initialLayout="@layout/simple_widget"
android:previewLayout="@layout/simple_widget"
android:minWidth="250dp"
android:minHeight="110dp"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"
android:previewImage="@drawable/example_appwidget_preview"
android:maxResizeWidth="1050dp"
android:maxResizeHeight="787dp"
android:targetCellWidth="5"
android:targetCellHeight="3"
/>

Final step: Register component in AndroidManifest.xml

<receiver
android:name=".resizeable.widget.SimpleGlanceAppWidgetReceiver"
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/simple_glance_widget_info" />
</receiver>

Stateful widget

Each GlanceAppWidget maintains its own preference to store data as key-value pair using Datastore preference.

To read the widget state use currentState method from local composition (i.e LocalState.current). currentState has two overloaded methods:

currentState with no argument returns a Preferences.

currentState with key as argument(i.e currentState(key)) returns a value associated with the key.

To update data into the preference we can use updateAppWidgetState()

Once the preference is updated using updateAppWidgetState() we have to manually call GlanceAppWidget.update() method to recompose the UI with latest data.

Let’s look at the below code to see how we are updating the boolean preference value to toggle the widget theme.

Stateful widget code

Stateful widget demo

Stateful widget demo

User interaction

As we discussed earlier Glance provides more intuitive APIs to handle user interaction and actionRunCallback is one of them.

  • actionRunCallback method returns an Action that can be attached to the onClick method of a button or any other Glance component with onClick attribute.
  • actionRunCallback method takes ActionCallback class as a type parameter and optional ActionParameters as method argument.

Let’s look at the below code whenever the button is clicked an instance of ToastActionCallback will be created and onRun method will be called on that with context, glanceId and the parameters we passed (i.e ActionParameters).

PS: glanceId is a unique id assigned to each GlanceAppWidget.

Handler user interaction with actionRunCallback (with action parameters)

Just like actionRunCallback we have other API to start a service (i.e actionStartService), start an activity (i.e actionStartActivity) or send a broadcast (i.e actionSendBroadcast) also.

Different size

SizeMode.Single

When the widget size mode is SizeMode.Single then the GlanceAppWidget provides a single UI. The width and height of the app widget will be the minimum width and height given in app widget info.

Single size mode widget demo

SizeMode.Exact

When the widget size mode is SizeMode.Exact then the GlanceAppWidget provides a UI for each size the App Widget can be. (i.e any one of the possible size from supported grid. click here for more detail).
Exact size mode widget demo

SizeMode.Responsive

When the widget size mode is SizeMode.Responsive then the GlanceAppWidget provides a UI for a fixed set of sizes.SizeMode.Responsive takes a set of fixed sizes from it's constructor.
Responsive size mode widget demo

Other updates on the widget framework

Apart from the Glance framework the existing widget framework also got some really good updates. lets see some of those.

Scalable widget preview

  • In Android 11 or below android:previewImage is used to show how the widget would look like
  • Since it’s an image, every time we update the widget design we have to change the image as well which would require some design effort.
  • Starting from Android 12 the widget preview can be derived from an XML layout, which results in better accuracy in reflecting how the actual widget will look like
<appwidget-provider
...
android:description="@string/app_widget_description"
android:targetCellWidth="3"
android:targetCellHeight="3"
android:previewLayout="@layout/my_widget_preview">
</appwidget-provider>
Ref https://developer.android.com/develop/ui/views/appwidgets/enhance

Other updates to widget framework

Take a look here to enhance your app-widgets and explore more updates on the widget framework starting from Android 12.

Repo with all the code samples is available here:

An important thing to notice here is that even though this change seems like. a breaking change none of the existing android widget frameworks was modified this entire Glance framework works on top of the existing android widget framework.

--

--

I'm a native android developer, currently exploring other domains to get a sense of technology breadth.