Do more with your widget in Android 12!

Murat Yener
Aug 27 · 7 min read

Do more with your Widget in Android 12!

This article is the second in a small series which I’ve written on updating your widget for Android 12. In the last part, we explored some easy ways to implement visual updates that are highly visible to app users. In this part, we’ll take a look at some of the more advanced features to make your widget more interactive, easier to configure, and provide a better UI experience on Android 12.

Easier configuration

Prior to Android 12, reconfiguring a widget meant that users had to delete their existing widget and add it again with the new configuration. Android 12 improves how widgets are configured in several ways and helps users to personalize the widgets more easily.

Reconfigurable widgets allow users to customize the widget to their desired settings. And with Android 12 they’ll no longer have to delete and re-add the widget to adjust these settings.

You can adjust the size and reconfigure the settings of the widget

To enable this, set the widgetFeatures attribute to reconfigurable in the appwidget-provider.

xml/app_widget_info_checkbox_list.xml
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<appwidget-provider
android:configure="com.example.android.appwidget.ListWidgetConfigureActivity"
android:widgetFeatures="reconfigurable"
... />

If your widget can rely on the default settings, you can skip launching the initial configuration activity and set up the widget with the default configuration in Android 12.

Let’s take a look at the sample widget to see how that works. In this use case, we want users to be able to choose between two different widget layouts, Grocery List and To-Do list. We’d like to use Grocery List as the default so the user doesn’t need to go through the configuration step unless they want to switch to To-Do list.

To implement this use case, you can store the user’s selection and return Grocery List as the default if no prior selection is made.

ListAppWidget.kt
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val layoutId = ListSharedPrefsUtil.loadWidgetLayoutIdPref(
context, appWidgetId
)
val remoteViews = if (layoutId == R.layout.widget_grocery_list) {
// Specify the maximum width and height in dp and a layout,
// which you want to use for the specified size
val viewMapping = mapOf(
SizeF(150f, 150f) to constructRemoteViews(
R.layout.widget_grocery_list
), SizeF(250f, 150f) to constructRemoteViews(
R.layout.widget_grocery_grid
)
)
RemoteViews(viewMapping)
} else {
constructRemoteViews(
layoutId
)
}
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)

Now that the widget is configured to provide the default configuration, you need to set the configuration_optional flag to the widgetFeatures attribute. This skips the extra configuration step and places your widget directly on the user’s home screen. While doing that, make sure to also add the reconfigurable flag so that users can change the applied default settings at a later time.

xml/app_widget_info_checkbox_list.xml
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<appwidget-provider
android:configure="com.example.android.appwidget.ListWidgetConfigureActivity"
android:widgetFeatures="reconfigurable|configuration_optional"
... />

With this change, the widget automatically uses the Grocery List layout when a user adds it to the home screen. Since the configuration activity is added to the configure attribute of appwidget-provider, it’s launched when a user long-presses the widget and clicks the edit/reconfigure button.

ListWidgetConfigureActivity will be launched when users click edit/reconfigure button

When the user configures the widget, this new configuration is stored in ListWidgetConfigureActivity.

ListWidgetConfigureActivity.kt
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
private fun onWidgetContainerClicked(@LayoutRes widgetLayoutResId: Int) {
ListSharedPrefsUtil.saveWidgetLayoutIdPref(this, appWidgetId, widgetLayoutResId)
// It is the responsibility of the configuration activity to update the app widget
val appWidgetManager = AppWidgetManager.getInstance(this)
ListAppWidget.updateAppWidget(this, appWidgetManager, appWidgetId)
// Make sure we pass back the original appWidgetId
val resultValue = Intent()
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
setResult(RESULT_OK, resultValue)
finish()
}

New and improved APIs

When it comes to devices running Android, there are many different form factors to choose from, whether it’s a phone, tablet, foldable, or another kind of product. Android 12 introduces refined size attributes and more flexible layouts to make widgets easier to customize and more reliable across different devices and screen sizes.

Android 12 adds new appwidget-provider attributes in addition to the existing minWidth, minHeight, minResizeWidth, and minResizeHeight.

You can use the new maxResizeWidth and maxResizeHeight attributes to define the maximum height and width which users can resize the widget. The new targetCellWidth and targetCellHeight attributes define the default widget size on the device home screen.

When targetCellWidth and targetCellHeight attributes are defined, devices running Android 12 will use those attributes instead of minWidth and minHeight. Devices running Android 11 and lower will continue to use the minWidth and minHeight attributes.

Note: The targetCellWidth and targetCellHeight attributes are defined in cells while maxResizeWidth and maxResizeHeight attributes are defined in dps.

xml/app_widget_info_checkbox_list.xml
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
<appwidget-provider
android:maxResizeWidth="240dp"
android:maxResizeHeight="180dp"
android:minWidth="180dp"
android:minHeight="110dp"
android:minResizeWidth="180dp"
android:minResizeHeight="110dp"
android:targetCellWidth="3"
android:targetCellHeight="2"
... />
In Android 12, targetCellWidth and targetCellHeight attributes are used instead of minWidth and minHeight

Although using sizing constraints can help users resize a widget to their needs, you may want to provide different layouts and types of content that will be used depending on the size of the widget. This also enables the system to display the widget in a different size without waking up the app.

To achieve this, first create a set of layouts for a range of sizes, then call the updateAppWidget() function and pass the set of layouts as shown in the sample below. The system automatically changes the layout when the widget size changes.

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
val viewMapping: MutableMap<SizeF, RemoteViews> = mutableMapOf()
// Specify the maximum width and height in dp and a layout, which
// you want to use for the specified size
val viewMapping = mapOf(
SizeF(150f, 110f) to RemoteViews(
context.packageName,
R.layout.widget_grocery_list
),
SizeF(250f, 110f) to RemoteViews(
context.packageName,
R.layout.widget_grocery_grid
),
)
appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))
//...
Responsive layouts offer better usability when users resize their widgets

Users can do more with widgets in Android 12 without needing to launch the app! With the new compound buttons you can make your widget more interactive. This doesn’t change the stateless nature of widgets, but lets you add a listener to observe the state change events. You can call RemoteResponse.fromPendingIntent() and pass a PendingIntent in the listener when a state change event occurs.

ItemsCollectionAppWidget.kt
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
remoteViews.setOnCheckedChangeResponse(
R.id.item_switch,
RemoteViews.RemoteResponse.fromPendingIntent(
onCheckedChangePendingIntent
)
)

On the other hand, if your widget has a list of controls you shouldn’t set PendingIntents on individual items of a collection since this results in poor performance. In this case, set a single PendingIntent template on the collection, call RemoteResponse.fromFillInIntent(), and pass a fillInIntent in the listener when a state change event occurs.

ItemsCollectionAppWidget.kt
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
remoteViews.setOnCheckedChangeResponse(
R.id.item_switch,
RemoteViews.RemoteResponse.fromFillInIntent(
onCheckedChangeFillInIntent
)
)
Widgets are more interactive with compound buttons

Android 12 introduces a new API to simplify passing a collection to populate lists in your widget. Previously, if you wanted to populate a ListView, GridView, StackView, or other view with a collection of items, you needed to implement RemoteViewsService to return RemoteViewsFactory. With the new setRemoteAdapter() API, you can simply pass a collection with your main RemoteViews. We are also working on androidx backport to support using this API on older versions of Android.

If the collection doesn’t use a constant set of layouts, you can use the setViewTypeCount() function to set the maximum number of layout ids that will be used by RemoteViews in this collection.

ItemsCollectionAppWidget.kt
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
remoteViews.setRemoteAdapter(
R.id.items_list_view,
RemoteViews.RemoteCollectionItems.Builder()
.addItem(/* id= */ ID_1, RemoteViews(...))
.addItem(/* id= */ ID_2, RemoteViews(...))
//...
.setViewTypeCount(MAX_NUM_DIFFERENT_REMOTE_VIEWS_LAYOUTS)
.build()
)

Summary

That’s it! Updating your existing widget to Android 12 will instantly bring a fresher look and make your widget more interactive.

Now that you’ve read about configurability and new or improved APIs in this article, check out the first part to learn how to update your widget’s design and offer a better user experience in the widget picker. To go even further, make sure to check out the sample code used throughout the article.

If you are building new widgets, keep an eye out for future announcements. We are working on making building new widgets even easier!

Android Developers

The official Android Developers publication on Medium