DEV Community

Cover image for Build Android App Widgets With Jetpack Glance
Sridhar Subramani
Sridhar Subramani

Posted on

Build Android App Widgets With Jetpack Glance

A modern approach to building android app widgets.

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"
        }
    }
Enter fullscreen mode Exit fullscreen mode

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!")
        }
    }
Enter fullscreen mode Exit fullscreen mode

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()
    }
Enter fullscreen mode Exit fullscreen mode

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/appwidget_preview"
        android:maxResizeWidth="1050dp"
        android:maxResizeHeight="787dp"
        android:targetCellWidth="5"
        android:targetCellHeight="3"
        />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 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)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 demoSingle 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 demoExact 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 demoResponsive 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>
Enter fullscreen mode Exit fullscreen mode

**Ref** [https://developer.android.com/develop/ui/views/appwidgets/enhance](https://developer.android.com/develop/ui/views/appwidgets/enhance)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:
https://github.com/sridhar-sp/android-playground/tree/main/

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.


References

Top comments (0)