Create an advanced widget

This page explains recommended practices for creating a more advanced widget that has a better user experience.

Optimizations for updating widget content

Updating widget content can be computationally expensive. To save battery consumption, you can optimize the update type, frequency, and timing.

Types of widget updates

There are three ways to update a widget: a full update, a partial update, and, in the case of a collection widget, a data refresh. Each has different computational costs and ramifications.

The following describes each update type and provides code snippets for each.

  • Full update: Call AppWidgetManager.updateAppWidget(int, android.widget.RemoteViews) to fully update the widget. This replaces the previously provided RemoteViews with a new RemoteViews. This is the most computationally expensive update.

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
      setTextViewText(R.id.textview_widget_layout1, "Updated text1")
      setTextViewText(R.id.textview_widget_layout2, "Updated text2")
    }
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout1, "Updated text1");
    remoteViews.setTextViewText(R.id.textview_widget_layout2, "Updated text2");
    appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
    
  • Partial update: Call AppWidgetManager.partiallyUpdateAppWidget to update parts of the widget. This merges the new RemoteViews with the previously provided RemoteViews. This method is ignored if a widget has not received at least one full update through updateAppWidget(int[], RemoteViews).

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    val remoteViews = RemoteViews(context.getPackageName(), R.layout.widgetlayout).also {
      setTextViewText(R.id.textview_widget_layout, "Updated text")
    }
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews)
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widgetlayout);
    remoteViews.setTextViewText(R.id.textview_widget_layout, "Updated text");
    appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews);
    
  • Collection data refresh: Call AppWidgetManager.notifyAppWidgetViewDataChanged to invalidate the data of a collection view in your widget. This triggers RemoteViewsFactory.onDataSetChanged. In the interim, the old data is displayed in the widget. Expensive tasks can be safely performed synchronously with this method.

    Kotlin

    val appWidgetManager = AppWidgetManager.getInstance(context)
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview)
    
    

    Java

    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_listview);
    

You can call these methods from anywhere in your app, as long as the app has the same UID as the corresponding AppWidgetProvider class.

Determine how often to update a widget

Widgets are updated periodically depending on the value provided for the updatePeriodMillis attribute. The widget can also be additionally updated in response to user interaction, broadcast updates, or both.

Periodic updates

You can control the frequency of the periodic update by specifying a value for the AppWidgetProviderInfo#updatePeriodMillis in the appwidget-provider XML. Each update triggers the AppWidgetProvider.onUpdate() method; you can place the code to update the widget here. However, consider alternatives for broadcast receiver updates if your widget either takes more than ten seconds to update (the period the system considers the BroadcastReceiver as non-responsive) or needs to load data asynchronously.

updatePeriodMillis does not support values smaller than 30 minutes. However, if you want to disable periodic updates, you can specify 0.

You can also allow users to adjust the frequency in a configuration—for example, they might want a stock ticker to update every 15 minutes, or maybe only four times a day. In this case, set the updatePeriodMillis to 0 and use WorkManager instead.

Widget update in response to a user interaction

Here are some recommended ways for updating the widget based on user interaction:

  • From an activity of the app: Directly call AppWidgetManager.updateAppWidget in response to a user interaction such as a user's click.

  • From remote interactions such as a notification or an app widget. Construct a PendingIntent, then update the widget from the invoked Activity, Broadcast, or Service. You can choose your own priority. For example, if you select a Broadcast for the PendingIntent, you could choose a foreground Broadcast to give the BroadcastReceiver more priority.

Widget update in response to a broadcast event

A specific example of a broadcast event that requires a widget to be updated is the user taking a new photo. In this case, you would want to update the widget when a new photo is detected.

You can schedule a job with JobScheduler and specify a broadcast as the trigger using the JobInfo.Builder.addTriggerContentUri method.

Alternatively, you can register a BroadcastReceiver for the broadcast—for example, listening for ACTION LOCALE_CHANGED. However, because this consumes device resources, use this with care and listen only to the specific broadcast. With the introduction of Broadcast limitations introduced in Android 7.0 (API level 24) and Android 8.0 (API level 26), apps can’t register implicit broadcasts in its manifest except for certain exceptions.

Considerations about updating a widget from a BroadcastReceiver

If the widget is updated from a BroadcastReceiver (including AppWidgetProvider), be aware of the following considerations regarding the duration and priority of a widget update.

Duration of update

As a rule, the system allows broadcast receivers (which usually run in the app’s main thread) to run for up to 10 seconds before considering them non-responsive and triggering an "Application Not Responding" (ANR) error. If it takes more time to update the widget, consider the following alternatives:

  • Schedule a task using WorkManager.
  • Give the receiver more time with the goAsync method. This allows receivers to execute for 30 seconds. However, any work you do here blocks further broadcasts until it completes, so taking advantage of this excessively can be counterproductive and cause later events to be received more slowly.

See Security considerations and best practices for more information.

Priority of update

By default, broadcasts—including ones made using AppWidgetProvider.onUpdate—run as background processes, which means that overloaded system resources can cause a delay in the invocation of the broadcast receiver. To give the broadcast more priority, make it a foreground process.

As an example, add the Intent.FLAG_RECEIVER_FOREGROUND to the Intent passed to the PendingIntent.getBroadcast when the user taps on a certain part of the widget.

Build accurate previews that include dynamic items

Figure 1: A widget preview displaying no list items

This section explains the recommended approach for displaying multiple items in a widget preview for a widget with a collection view (that is, a widget that uses a ListView, GridView, or StackView).

If your widget uses one of these views, creating a scalable preview by directly providing the actual widget layout causes a degraded experience where the widget preview displays no items. This occurs because collection view data is set dynamically at runtime, and looks similar to the image shown in Figure 1.

To ensure that previews of widgets with collection views are properly displayed in the widget picker, we recommend maintaining a separate layout file designated for the preview only. This separate layout file includes the actual widget layout and a placeholder collection view with fake items. For example, you can mimic a ListView by providing a placeholder LinearLayout with several fake list items.

To illustrate an example for a ListView, start with a separate layout file:

// res/layout/widget_preview.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="@drawable/widget_background"
   android:orientation="vertical">

    // Include the actual widget layout that contains ListView.
    <include
        layout="@layout/widget_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    // The amount of fake items you include depends on the values you provide
    // for minHeight or targetCellHeight in the AppWidgetProviderInfo definition.

    <TextView android:text="@string/fake_item1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

    <TextView android:text="@string/fake_item2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginVertical="?attr/appWidgetInternalPadding" />

</LinearLayout>

Then, you would specify the preview layout file when providing the previewLayout attribute of the AppWidgetProviderInfo metadata. Note that you would still specify the actual widget layout for the initialLayout attribute and use the actual widget layout when constructing a RemoteViews at runtime.

<appwidget-provider
    previewLayout="@layout/widget_previe"
    initialLayout="@layout/widget_view" />

Complex list items

In the example in the previous section, it was simple to provide fake list items since the list items were simple TextView objects. It can be more complex to provide fake items if the items are complex layouts.

Consider a list item that is defined in widget_list_item.xml and consists of two TextView objects.

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <TextView android:id="@id/title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_title" />

    <TextView android:id="@id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/fake_content" />
</LinearLayout>

To provide fake list items, you can include the layout multiple times, but this will cause each list item to be identical. In order to provide unique list items, follow these steps:

  1. Create a set of attributes for the text values:

    <resources>
        <attr name="widgetTitle" format="string" />
        <attr name="widgetContent" format="string" />
    </resources>
    
  2. Use these attributes to set the text:

    <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
        <TextView android:id="@id/title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetTitle" />
    
        <TextView android:id="@id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="?widgetContent" />
    </LinearLayout>
    
  3. Create as many styles as required for the preview. Redefine the values in each style:

    <resources>
    
        <style name="Theme.Widget.ListItem">
            <item name="widgetTitle"></item>
            <item name="widgetContent"></item>
        </style>
        <style name="Theme.Widget.ListItem.Preview1">
            <item name="widgetTitle">Fake Title 1</item>
            <item name="widgetContent">Fake content 1</item>
        </style>
        <style name="Theme.Widget.ListItem.Preview2">
            <item name="widgetTitle">Fake title 2</item>
            <item name="widgetContent">Fake content 2</item>
        </style>
    
    </resources>
    
  4. Finally, apply the styles on the fake items in the preview layout.

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" ...>
    
        <include layout="@layout/widget_view" ... /> 
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview1" /> 
    
        <include layout="@layout/widget_list_item"
            android:theme="@style/Theme.Widget.ListItem.Preview2" /> 
    
    </LinearLayout>