Android 12 widgets improvements

Android 12 revamps the existing Widgets API to improve the user and developer experience in the platform and launchers. Use this guide to learn how to ensure your widget is compatible with Android 12, and also as a reference for APIs for refreshing your existing widget.

Alt text

Ensure your widget is compatible with Android 12

Widgets in Android 12 have rounded corners. When an app widget is used on a device running Android 12 or higher, the launcher automatically identifies the widget's background and crops it to have rounded corners.

In this scenario, your widget may not display properly in either of the following conditions:

  • The widget contains content in the corners: This may cause some content in the corner area to be cropped.

  • The widget uses a background that is not susceptible to cropping. This includes a transparent background, empty views or layouts, or any other kind of special background not prone to cropping. The system may not be able to correctly identify the background to use.

If your widget will be affected by this change, we recommend refreshing it with rounded corners (as described in the following section) to ensure it displays properly.

Use the sample

To see all these new APIs in action, check out our sample list widget.

Implement rounded corners

Android 12 introduces the following system parameters to set the radii of your widget's rounded corners:

The following example shows a widget that uses system_app_widget_background_radius for the corner of the widget and system_app_widget_inner_radius for views inside the widget.

Alt text

1 Corner of the widget.

2 Corner of a view inside the widget.

Backward-compatibility with rounded corners

To ensure widget compatibility with previous versions of Android, we recommend defining custom attributes and using a custom theme to override them for Android 12, as shown in the following examples of XML files:

/values/attrs.xml

<resources>
  <attr name="backgroundRadius" format="dimension" />
</resources>

/values/styles.xml

<resources>
  <style name="MyWidgetTheme">
    <item name="backgroundRadius">@dimen/my_background_radius_dimen</item>
  </style>
</resources>

/values-31/styles.xml

<resources>
  <style name="MyWidgetTheme" parent="@android:style/Theme.DeviceDefault.DayNight">
    <item name="backgroundRadius">@android:dimen/system_app_widget_background_radius</item>
  </style>
</resources>

/drawable/my_widget_background.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <corners android:radius="?attr/backgroundRadius" />
  ...
</shape>

/layout/my_widget_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  ...
  android:background="@drawable/my_widget_background" />

Apply dynamic colors

In Android 12, a widget can use the device theme colors for buttons, backgrounds, and other components. This enables smoother transitions and consistency across different widgets.

In the following example, the device theme color is “brownish”, causing the accent color and widget background to adapt. You can achieve this by using the system's default theme (@android:style/Theme.DeviceDefault.DayNight) and its color attributes. Some commonly-used color attributes are:

  • ?android:attr/colorAccent
  • ?android:attr/colorBackground
  • ?android:attr/textColorPrimary and ?android:attr/textColorSecondary
Widget in light mode theme
Widget in light theme
Widgets in dark mode theme
Widget in dark theme

Backward-compatibility with dynamic colors

We recommend creating a custom theme and overriding it for Android 12. The following examples show how to do this with various types of XML files:

/values/styles.xml

<resources>
  <style name="MyWidget.TextView">
    <item name="android:textColor">@color/my_text_color</item>
  </style>
  <style name="MyWidgetTheme">
    <item name="textViewStyle">@style/MyWidget.TextView</item>
  </style>
</resources>

/values-31/styles.xml

<resources>
  <style name="MyWidgetTheme" parent="Theme.DeviceDefault.DayNight" />
</resources>

/layout/my_widget_layout.xml

<resources>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:theme="@style/MyWidgetTheme" />
</resources>

Make it easier to personalize widgets

If you specify a configuration activity with the configure attribute of appwidget-provider, the App Widget host launches that activity immediately after a user adds the widget to their home screen.

Android 12 adds new options to let you provide a better configuration experience for users.

Enable users to reconfigure placed widgets

To configure widgets that are labeled as reconfigurable, users can long-press the widget. This displays a Reconfigure button, which they can tap to change settings.

Alt text

1 Reconfigure button.

Specify the reconfigurable flag in the widgetFeatures attribute of appwidget-provider:

<appwidget-provider
  ...
  android:configure="com.myapp.WidgetConfigActivity"
  android:widgetFeatures="reconfigurable">
</appwidget-provider>

Use the widget's default configuration

If you’d like your widget to use its default configuration when a user adds it, you can skip the configuration step by specifying both the configuration_optional and reconfigurable flags in the widgetFeatures field. This bypasses launching the configuration activity after a user adds the widget. (As mentioned previously, the user can still reconfigure the widget afterwards.)

For example, a clock widget could bypass the initial configuration and show the device time zone by default.

<appwidget-provider
  ...
  android:configure="com.myapp.WidgetConfigActivity"
  android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>

Backward-compatibility with widget configuration options

Your app can use the configuration_optional and reconfigurable flags in previous versions of Android. However, these flags won't have any effect and the system would still launches the configuration activity.

Add new compound buttons

Android 12 adds new support for stateful behavior using the following existing components:

The widget is still stateless. Your app must store the state and register for state change events.

Alt text

The following code example shows how to implement these components.

Kotlin

// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true)

// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2)

// Listen for check changes. The intent will have an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
  R.id.my_checkbox,
  RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent)
)

Java

// Check the view.
remoteView.setCompoundButtonChecked(R.id.my_checkbox, true);

// Check a radio group.
remoteView.setRadioGroupChecked(R.id.my_radio_group, R.id.radio_button_2);

// Listen for check changes. The intent will have an extra with the key
// EXTRA_CHECKED that specifies the current checked state of the view.
remoteView.setOnCheckedChangeResponse(
  R.id.my_checkbox,
  RemoteViews.RemoteResponse.fromPendingIntent(onCheckedChangePendingIntent)
);

Backward-compatibility with widget compound buttons

Provide two different layouts, with one targeting devices running Android 12 or higher (res/layout-v31) and the other targeting previous versions of Android (in the default res/layout folder).

Use improved APIs for widget sizes and layouts

Starting in Android 12, you can provide more refined size attributes and more flexible layouts and by doing the following:

  1. Specify additional widget sizing constraints

  2. Provide responsive layouts or exact layouts

Specify additional widget sizing constraints

Android 12 adds new APIs allowing you to ensure your widget is sized more reliably across different devices with varying screen sizes.

In addition to the existing minWidth, minHeight, minResizeWidth, and minResizeHeight attributes, use the following new appwidget-provider attributes:

The following XML describes how to use the sizing attributes.

<appwidget-provider
  ...
  android:targetCellWidth="3"
  android:targetCellHeight="2"
  android:maxResizeWidth="250dp"
  android:maxResizeHeight="110dp">
</appwidget-provider>

Provide responsive layouts

If the layout needs to change depending on the size of the widget, we recommend creating a small set of layouts, each valid for a range of sizes. (If this isn’t possible, another option is to provide layouts based on the exact widget size at runtime.)

Implementing this feature allows for smoother scaling and overall better system health; this is because the system doesn't have to wake up the app every time it displays the widget in a different size.

The following code example shows how to provide a list of layouts.

Kotlin

override fun onUpdate(...) {
  val smallView = ...
  val tallView = ...
  val wideView = ...

  val viewMapping: Map<SizeF, RemoteViews> = mapOf(
    SizeF(100f, 100f) to smallView,
    SizeF(100f, 200f) to tallView,
    SizeF(200f, 100f) to wideView
  )
  val remoteViews = RemoteViews(viewMapping)

  appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

@Override
public void onUpdate(...) {
  RemoteViews smallView = ...;
  RemoteViews tallView = ...;
  RemoteViews wideView = ...;

  Map<SizeF, RemoteViews> viewMapping = new ArrayMap<>();
  viewMapping.put(new SizeF(100f, 100f), smallView);
  viewMapping.put(new SizeF(100f, 200f), tallView);
  viewMapping.put(new SizeF(200f, 100f), wideView);
  RemoteViews remoteViews = new RemoteViews(viewMapping);

  appWidgetManager.updateAppWidget(id, remoteViews);
}

Provide exact layouts

If a small set of responsive layouts isn't feasible, you can instead provide different layouts tailored to the sizes at which the widget is shown. This is typically two sizes for phones (portrait and landscape mode) and four sizes for foldables.

To implement this solution, your app needs to perform the following steps:

  1. Overload AppWidgetProvider#onAppWidgetOptionsChanged(...), which is called when the set of sizes changes.

  2. Call getAppWidgetManager#getAppWidgetOptions(...), which returns a Bundle containing the sizes.

  3. Access the AppWidgetManager.OPTION_APPWIDGET_SIZES key from the Bundle.

The following code example shows how to provide exact layouts.

Kotlin

// Create the RemoteViews for the given size.
private fun createRemoteViews(size: SizeF): RemoteViews { }

override fun onAppWidgetOptionsChanged(
  context: Context,
  manager: AppWidgetManager,
  id: Int,
  newOptions: Bundle?
) {
  super.onAppWidgetOptionsChanged(context, manager, id, newOptions)
  // Get the new sizes.
  val sizes = newOptions?.getParcelableArrayList<SizeF>(
    AppWidgetManager.OPTION_APPWIDGET_SIZES
  )
  // Check that the list of sizes is provided by the launcher.
  if (sizes.isNullOrEmpty()) {
    return
  }
  // Map the sizes to the desired RemoteViews
  val remoteViews = RemoteViews(sizes.associateWith(::createRemoteViews))
  appWidgetManager.updateAppWidget(id, remoteViews)
}

Java

// Create the RemoteViews for the given size.
private RemoteViews createRemoteViews(SizeF size) { }

@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager
  appWidgetManager, int appWidgetId, Bundle newOptions) {
    super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
    // Get the new sizes.
    ArrayList<SizeF> sizes =
      newOptions.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES);
    // Check that the list of sizes is provided by the launcher.
    if (sizes == null || sizes.isEmpty()) {
      return;
    }
    // Map the sizes to the desired RemoteViews.
    Map<SizesF, RemoteViews> viewMapping = new ArrayMap<>();
    for (SizeF size : sizes) {
      viewMapping.put(size, createRemoteViews(size));
    }
    RemoteViews remoteViews = new RemoteViews(viewMapping);
    appWidgetManager.updateAppWidget(id, remoteViews);
}

Backward-compatibility with widget layout sizes

Previously, it was possible to get the widget's size ranges using the OPTION_APPWIDGET_MIN_WIDTH, OPTION_APPWIDGET_MIN_HEIGHT, OPTION_APPWIDGET_MAX_WIDTH, and OPTION_APPWIDGET_MAX_HEIGHT extras and estimate the widget's size, but that logic doesn't work in all situations. For widgets targeting Android 12, we recommend switching to providing responsive or exact layouts as explained previously.

Improve your app's widget picker experience

Android 12 enables you to improve the widget picker experience for your app by adding dynamic widget previews and widget descriptions.

Add scalable widget previews to the widget picker

In Android 12, the widget preview displayed in the widget picker consists of a scalable preview, which you’ll provide as an XML layout set to the widget's default size. Previously, the widget preview was a static drawable resource, in some cases leading to previews not accurately reflecting widgets after they were added to the home screen.

To implement scalable widget previews, use the previewLayout attribute of the appwidget-provider element to provide an XML layout instead:

<appwidget-provider
  ...
  android:previewLayout="@layout/my_widget_preview">
</appwidget-provider>

Ideally, this should be the same layout as the actual widget with realistic default or test values.

Backward-compatibility with scalable widget previews

To enable widget pickers on Android 11 or lower to show previews of your widget, continue specifying the previewImage attribute.

If you make changes to the widget’s appearance, make sure to also update the preview image.

Add a description for your widget

In Android 12, you can optionally provide a description for the widget picker to display for your widget.

Alt text

Provide a description for your widget using the description attribute of appwidget-provider:

<appwidget-provider
  ...
  android:description="@string/my_widget_description">
</appwidget-provider>

Backward-compatibility with widget descriptions

Your app can use the widgetDescription attribute on previous versions of Android, but it won’t be shown in the widget picker.

Enable smoother transitions

In Android 12, launchers provide a smoother transition when a user launches your app from a widget.

To enable this improved transition, use @android:id/background or android.R.id.background to identify your background element:

// Top level layout of the widget.
<LinearLayout
  ...
  android:id="@android:id/background">
</LinearLayout>

Backward-compatibility with smoother transitions

Your app can use @android:id/background in previous versions of Android, but it won’t have any effect.

Use simplified RemoteViews collections

Android 12 adds the setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) method, which lets your app pass along a collection directly when populating a ListView. Previously, when using a ListView, it was necessary to implement and declare a RemoteViewsService to return RemoteViewsFactory.

If the collection doesn’t use a constant set of layouts (in other words, if some items are only sometimes present), use setViewTypeCount to specify the maximum number of unique layouts the collection can contain.

Here’s an example of how to implement simplified RemoteViews collections.

Kotlin

remoteView.setRemoteAdapter(
  R.id.list_view,
  RemoteViews.RemoteCollectionItems.Builder()
    .addItem(/* id= */ ID_1, RemoteViews(...))
    .addItem(/* id= */ ID_2, RemoteViews(...))
    ...
    .setViewTypeCount(MAX_NUM_DIFFERENT_REMOTE_VIEWS_LAYOUTS)
    .build()
)

Java

remoteView.setRemoteAdapter(
  R.id.list_view,
  new RemoteViews.RemoteCollectionItems.Builder()
    .addItem(/* id= */ ID_1, new RemoteViews(...))
    .addItem(/* id= */ ID_2, new RemoteViews(...))
    ...
    .setViewTypeCount(MAX_NUM_DIFFERENT_REMOTE_VIEWS_LAYOUTS)
    .build()
);

Use runtime modification of RemoteViews

Android 12 adds several RemoteViews methods that allow for runtime modification of RemoteViews attributes. See the RemoteViews API reference for the full list of added methods.

The following code example shows how to use a few of the new methods.

Kotlin

// Set the colors of a progress bar at runtime.
remoteView.setColorStateList(R.id.progress, "setProgressTintList", createProgressColorStateList())

// Specify exact sizes for margins.
remoteView.setViewLayoutMargin(R.id.text, RemoteViews.MARGIN_END, 8f, TypedValue.COMPLEX_UNIT_DP)

Java

// Set the colors of a progress bar at runtime.
remoteView.setColorStateList(R.id.progress, "setProgressTintList", createProgressColorStateList());

// Specify exact sizes for margins.
remoteView.setViewLayoutMargin(R.id.text, RemoteViews.MARGIN_END, 8f, TypedValue.COMPLEX_UNIT_DP);