Use collection widgets

Collection widgets specialize in displaying many elements of the same type, such as collections of pictures from a gallery app, articles from a news app, or messages from a communication app. Collection widgets typically focus on two use cases: browsing the collection and opening an element of the collection to its detail view. Collection widgets can scroll vertically.

These widgets use the RemoteViewsService to display collections that are backed by remote data, such as from a content provider. The widget presents the data using one of the following view types, which are known as collection views:

ListView
A view that shows items in a vertically scrolling list.
GridView
A view that shows items in a two-dimensional scrolling grid.
StackView
A stacked card view—kind of like a rolodex—where the user can flick the front card up or down to see the previous or next card, respectively.
AdapterViewFlipper
An adapter-backed simple ViewAnimator that animates between two or more views. Only one child is shown at a time.

Because these collection views display collections backed by remote data, they use an Adapter to bind their user interface to their data. An Adapter binds individual items from a set of data to individual View objects.

And because these collection views are backed by adapters, the Android framework must include extra architecture to support their use in widgets. In the context of a widget, the Adapter is replaced by a RemoteViewsFactory, which is a thin wrapper around the Adapter interface. When requested for a specific item in the collection, the RemoteViewsFactory creates and returns the item for the collection as a RemoteViews object. To include a collection view in your widget, implement RemoteViewsService and RemoteViewsFactory.

RemoteViewsService is a service that lets a remote adapter request RemoteViews objects. RemoteViewsFactory is an interface for an adapter between a collection view—such as ListView, GridView, and StackView—and the underlying data for that view. From the StackWidget sample, here is an example of the boilerplate code to implement this service and interface:

Kotlin

class StackWidgetService : RemoteViewsService() {

    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
        return StackRemoteViewsFactory(this.applicationContext, intent)
    }
}

class StackRemoteViewsFactory(
        private val context: Context,
        intent: Intent
) : RemoteViewsService.RemoteViewsFactory {

// See the RemoteViewsFactory API reference for the full list of methods to
// implement.

}

Java

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

// See the RemoteViewsFactory API reference for the full list of methods to
// implement.

}

Sample application

The code excerpts in this section are also drawn from the StackWidget sample:

Figure 1. A StackWidget.

This sample consists of a stack of ten views that display the values zero through nine. The sample widget has these primary behaviors:

  • The user can vertically fling the top view in the widget to display the next or previous view. This is a built-in StackView behavior.

  • Without any user interaction, the widget automatically advances through its views in sequence, like a slideshow. This is due to the setting android:autoAdvanceViewId="@id/stack_view" in the res/xml/stackwidgetinfo.xml file. This setting applies to the view ID, which in this case is the view ID of the stack view.

  • If the user touches the top view, the widget displays the Toast message "Touched view n," where n is the index (position) of the touched view. For more discussion on how to implement behaviors, see the Add behavior to individual items section.

Implement widgets with collections

To implement a widget with collections, follow the procedure to implement any widget, followed by a few additional steps: modify the manifest, add a collection view to the widget layout, and modify your AppWidgetProvider subclass.

Manifest for widgets with collections

Beyond the requirements listed in Declare a widget in the manifest, you need to make it possible for widgets with collections to bind to your RemoteViewsService. Do this by declaring the service in your manifest file with the permission BIND_REMOTEVIEWS. This prevents other applications from freely accessing your widget's data.

For example, when creating a widget that uses RemoteViewsService to populate a collection view, the manifest entry might look like this:

<service android:name="MyWidgetService"
    android:permission="android.permission.BIND_REMOTEVIEWS" />

In this example, android:name="MyWidgetService" refers to your subclass of RemoteViewsService.

Layout for widgets with collections

The main requirement for your widget layout XML file is that it include one of the collection views: ListView, GridView, StackView, or AdapterViewFlipper. Here is the widget_layout.xml file for the StackWidget sample:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <StackView
        android:id="@+id/stack_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:loopViews="true" />
    <TextView
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:background="@drawable/widget_item_background"
        android:textColor="#ffffff"
        android:textStyle="bold"
        android:text="@string/empty_view_text"
        android:textSize="20sp" />
</FrameLayout>

Note that empty views must be siblings of the collection view for which the empty view represents empty state.

In addition to the layout file for your entire widget, create another layout file that defines the layout for each item in the collection—for example, a layout for each book in a collection of books. The StackWidget sample has only one item layout file, widget_item.xml, since all items use the same layout.

AppWidgetProvider class for widgets with collections

As with regular widgets, the bulk of the code in your AppWidgetProvider subclass typically goes in onUpdate(). The major difference in your implementation for onUpdate() when creating a widget with collections is that you must call setRemoteAdapter(). This tells the collection view where to get its data. The RemoteViewsService can then return your implementation of RemoteViewsFactory, and the widget can serve up the appropriate data. When you call this method, pass an intent that points to your implementation of RemoteViewsService and the widget ID that specifies the widget to update.

For example, here's how the StackWidget sample implements the onUpdate() callback method to set the RemoteViewsService as the remote adapter for the widget collection:

Kotlin

override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
) {
    // Update each of the widgets with the remote adapter.
    appWidgetIds.forEach { appWidgetId ->

        // Set up the intent that starts the StackViewService, which
        // provides the views for this collection.
        val intent = Intent(context, StackWidgetService::class.java).apply {
            // Add the widget ID to the intent extras.
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
            data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
        }
        // Instantiate the RemoteViews object for the widget layout.
        val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
            // Set up the RemoteViews object to use a RemoteViews adapter.
            // This adapter connects to a RemoteViewsService through the
            // specified intent.
            // This is how you populate the data.
            setRemoteAdapter(R.id.stack_view, intent)

            // The empty view is displayed when the collection has no items.
            // It must be in the same layout used to instantiate the
            // RemoteViews object.
            setEmptyView(R.id.stack_view, R.id.empty_view)
        }

        // Do additional processing specific to this widget.

        appWidgetManager.updateAppWidget(appWidgetId, views)
    }
    super.onUpdate(context, appWidgetManager, appWidgetIds)
}

Java

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    // Update each of the widgets with the remote adapter.
    for (int i = 0; i < appWidgetIds.length; ++i) {

        // Set up the intent that starts the StackViewService, which
        // provides the views for this collection.
        Intent intent = new Intent(context, StackWidgetService.class);
        // Add the widget ID to the intent extras.
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
        intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
        // Instantiate the RemoteViews object for the widget layout.
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        // Set up the RemoteViews object to use a RemoteViews adapter.
        // This adapter connects to a RemoteViewsService through the specified
        // intent.
        // This is how you populate the data.
        views.setRemoteAdapter(R.id.stack_view, intent);

        // The empty view is displayed when the collection has no items.
        // It must be in the same layout used to instantiate the RemoteViews
        // object.
        views.setEmptyView(R.id.stack_view, R.id.empty_view);

        // Do additional processing specific to this widget.

        appWidgetManager.updateAppWidget(appWidgetIds[i], views);
    }
    super.onUpdate(context, appWidgetManager, appWidgetIds);
}

Persist data

As described on this page, the RemoteViewsService subclass provides the RemoteViewsFactory used to populate the remote collection view.

Specifically, perform these steps:

  1. Subclass RemoteViewsService. RemoteViewsService is the service through which a remote adapter can request RemoteViews.

  2. In your RemoteViewsService subclass, include a class that implements the RemoteViewsFactory interface. RemoteViewsFactory is an interface for an adapter between a remote collection view—such as ListView, GridView, StackView—and the underlying data for that view. Your implementation is responsible for making a RemoteViews object for each item in the dataset. This interface is a thin wrapper around Adapter.

You can’t rely on a single instance of your service, or any data it contains, to persist. Don't store data in your RemoteViewsService unless it is static. If you want your widget’s data to persist, the best approach is to use a ContentProvider whose data persists beyond the process lifecycle. For example, a grocery store widget can store the state of each grocery list item in a persistent location, such as a SQL database.

The primary contents of the RemoteViewsService implementation is its RemoteViewsFactory, described in the following section.

RemoteViewsFactory interface

Your custom class that implements the RemoteViewsFactory interface provides the widget with the data for the items in its collection. To do this, it combines your widget item XML layout file with a source of data. This source of data can be anything from a database to a simple array. In the StackWidget sample, the data source is an array of WidgetItems. The RemoteViewsFactory functions as an adapter to glue the data to the remote collection view.

The two most important methods you need to implement for your RemoteViewsFactory subclass are onCreate() and getViewAt().

The system calls onCreate() when creating your factory for the first time. This is where you set up any connections or cursors to your data source. For example, the StackWidget sample uses onCreate() to initialize an array of WidgetItem objects. When your widget is active, the system accesses these objects using their index position in the array and displays the text they contain.

Here is an excerpt from the StackWidget sample's RemoteViewsFactory implementation that shows portions of the onCreate() method:

Kotlin

private const val REMOTE_VIEW_COUNT: Int = 10

class StackRemoteViewsFactory(
        private val context: Context
) : RemoteViewsService.RemoteViewsFactory {

    private lateinit var widgetItems: List<WidgetItem>

    override fun onCreate() {
        // In onCreate(), set up any connections or cursors to your data
        // source. Heavy lifting, such as downloading or creating content,
        // must be deferred to onDataSetChanged() or getViewAt(). Taking
        // more than 20 seconds on this call results in an ANR.
        widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
        ...
    }
    ...
}

Java

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private static final int REMOTE_VIEW_COUNT = 10;
    private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>();

    public void onCreate() {
        // In onCreate(), setup any connections or cursors to your data
        // source. Heavy lifting, such as downloading or creating content,
        // must be deferred to onDataSetChanged() or getViewAt(). Taking
        // more than 20 seconds on this call results in an ANR.
        for (int i = 0; i < REMOTE_VIEW_COUNT; i++) {
            widgetItems.add(new WidgetItem(i + "!"));
        }
        ...
    }
...

The RemoteViewsFactory method getViewAt() returns a RemoteViews object corresponding to the data at the specified position in the data set. Here is an excerpt from the StackWidget sample's RemoteViewsFactory implementation:

Kotlin

override fun getViewAt(position: Int): RemoteViews {
    // Construct a remote views item based on the widget item XML file
    // and set the text based on the position.
    return RemoteViews(context.packageName, R.layout.widget_item).apply {
        setTextViewText(R.id.widget_item, widgetItems[position].text)
    }
}

Java

public RemoteViews getViewAt(int position) {
    // Construct a remote views item based on the widget item XML file
    // and set the text based on the position.
    RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_item);
    views.setTextViewText(R.id.widget_item, widgetItems.get(position).text);
    return views;
}

Add behavior to individual items

The preceding sections show how to bind your data to your widget collection. But what if you want to add dynamic behavior to the individual items in your collection view?

As described in Handle events with the onUpdate() class, you normally use setOnClickPendingIntent() to set an object's click behavior—such as to cause a button to launch an Activity. But this approach is not allowed for child views in an individual collection item. For example, you can use setOnClickPendingIntent() to set up a global button in the Gmail widget that launches the app, for example, but not on the individual list items.

Instead, to add click behavior to individual items in a collection, use setOnClickFillInIntent(). This entails setting up a pending intent template for your collection view and then setting a fill-in intent on each item in the collection via your RemoteViewsFactory.

This section uses the StackWidget sample to describe how to add behavior to individual items. In the StackWidget sample, if the user touches the top view, the widget displays the Toast message "Touched view n," where n is the index (position) of the touched view. This is how it works:

  • The StackWidgetProvider—an AppWidgetProvider subclass—creates a pending intent with a custom action called TOAST_ACTION.

  • When the user touches a view, the intent fires and it broadcasts TOAST_ACTION.

  • This broadcast is intercepted by the StackWidgetProvider class's onReceive() method, and the widget displays the Toast message for the touched view. The data for the collection items is provided by the RemoteViewsFactory through the RemoteViewsService.

Set up the pending intent template

The StackWidgetProvider (an AppWidgetProvider subclass) sets up a pending intent. Individual items of a collection can't set up their own pending intents. Instead, the collection as a whole sets up a pending intent template, and the individual items set a fill-in intent to create unique behavior on an item-by-item basis.

This class also receives the broadcast that is sent when the user touches a view. It processes this event in its onReceive() method. If the intent's action is TOAST_ACTION, the widget displays a Toast message for the current view.

Kotlin

const val TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION"
const val EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM"

class StackWidgetProvider : AppWidgetProvider() {

    ...

    // Called when the BroadcastReceiver receives an Intent broadcast.
    // Checks whether the intent's action is TOAST_ACTION. If it is, the
    // widget displays a Toast message for the current item.
    override fun onReceive(context: Context, intent: Intent) {
        val mgr: AppWidgetManager = AppWidgetManager.getInstance(context)
        if (intent.action == TOAST_ACTION) {
            val appWidgetId: Int = intent.getIntExtra(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID
            )
            // EXTRA_ITEM represents a custom value provided by the Intent
            // passed to the setOnClickFillInIntent() method to indicate the
            // position of the clicked item. See StackRemoteViewsFactory in
            // Set the fill-in Intent for details.
            val viewIndex: Int = intent.getIntExtra(EXTRA_ITEM, 0)
            Toast.makeText(context, "Touched view $viewIndex", Toast.LENGTH_SHORT).show()
        }
        super.onReceive(context, intent)
    }

    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
    ) {
        // Update each of the widgets with the remote adapter.
        appWidgetIds.forEach { appWidgetId ->

            // Sets up the intent that points to the StackViewService that
            // provides the views for this collection.
            val intent = Intent(context, StackWidgetService::class.java).apply {
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                // When intents are compared, the extras are ignored, so embed
                // the extra sinto the data so that the extras are not ignored.
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
            }
            val rv = RemoteViews(context.packageName, R.layout.widget_layout).apply {
                setRemoteAdapter(R.id.stack_view, intent)

                // The empty view is displayed when the collection has no items.
                // It must be a sibling of the collection view.
                setEmptyView(R.id.stack_view, R.id.empty_view)
            }

            // This section makes it possible for items to have individualized
            // behavior. It does this by setting up a pending intent template.
            // Individuals items of a collection can't set up their own pending
            // intents. Instead, the collection as a whole sets up a pending
            // intent template, and the individual items set a fillInIntent
            // to create unique behavior on an item-by-item basis.
            val toastPendingIntent: PendingIntent = Intent(
                    context,
                    StackWidgetProvider::class.java
            ).run {
                // Set the action for the intent.
                // When the user touches a particular view, it has the effect of
                // broadcasting TOAST_ACTION.
                action = TOAST_ACTION
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))

                PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT)
            }
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent)

            appWidgetManager.updateAppWidget(appWidgetId, rv)
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds)
    }
}

Java

public class StackWidgetProvider extends AppWidgetProvider {
    public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";
    public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";

    ...

    // Called when the BroadcastReceiver receives an Intent broadcast.
    // Checks whether the intent's action is TOAST_ACTION. If it is, the
    // widget displays a Toast message for the current item.
    @Override
    public void onReceive(Context context, Intent intent) {
        AppWidgetManager mgr = AppWidgetManager.getInstance(context);
        if (intent.getAction().equals(TOAST_ACTION)) {
            int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
            // EXTRA_ITEM represents a custom value provided by the Intent
            // passed to the setOnClickFillInIntent() method to indicate the
            // position of the clicked item. See StackRemoteViewsFactory in
            // Set the fill-in Intent for details.
            int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
            Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();
        }
        super.onReceive(context, intent);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // Update each of the widgets with the remote adapter.
        for (int i = 0; i < appWidgetIds.length; ++i) {

            // Sets up the intent that points to the StackViewService that
            // provides the views for this collection.
            Intent intent = new Intent(context, StackWidgetService.class);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            // When intents are compared, the extras are ignored, so embed
            // the extras into the data so that the extras are not
            // ignored.
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);

            // The empty view is displayed when the collection has no items. It
            // must be a sibling of the collection view.
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);

            // This section makes it possible for items to have individualized
            // behavior. It does this by setting up a pending intent template.
            // Individuals items of a collection can't set up their own pending
            // intents. Instead, the collection as a whole sets up a pending
            // intent template, and the individual items set a fillInIntent
            // to create unique behavior on an item-by-item basis.
            Intent toastIntent = new Intent(context, StackWidgetProvider.class);
            // Set the action for the intent.
            // When the user touches a particular view, it has the effect of
            // broadcasting TOAST_ACTION.
            toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
            toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);

            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }
}

Set the fill-in intent

Your RemoteViewsFactory must set a fill-in intent on each item in the collection. This makes it possible to distinguish the individual on-click action of a given item. The fill-in intent is then combined with the PendingIntent template to determine the final intent that is executed when the item is tapped.

Kotlin

private const val REMOTE_VIEW_COUNT: Int = 10

class StackRemoteViewsFactory(
        private val context: Context,
        intent: Intent
) : RemoteViewsService.RemoteViewsFactory {

    private lateinit var widgetItems: List<WidgetItem>
    private val appWidgetId: Int = intent.getIntExtra(
            AppWidgetManager.EXTRA_APPWIDGET_ID,
            AppWidgetManager.INVALID_APPWIDGET_ID
    )

    override fun onCreate() {
        // In onCreate(), set up any connections or cursors to your data source.
        // Heavy lifting, such as downloading or creating content, must be
        // deferred to onDataSetChanged() or getViewAt(). Taking more than 20
        // seconds on this call results in an ANR.
        widgetItems = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
        ...
    }
    ...

    override fun getViewAt(position: Int): RemoteViews {
        // Construct a remote views item based on the widget item XML file
        // and set the text based on the position.
        return RemoteViews(context.packageName, R.layout.widget_item).apply {
            setTextViewText(R.id.widget_item, widgetItems[position].text)

            // Set a fill-intent to fill in the pending intent template.
            // that is set on the collection view in StackWidgetProvider.
            val fillInIntent = Intent().apply {
                Bundle().also { extras ->
                    extras.putInt(EXTRA_ITEM, position)
                    putExtras(extras)
                }
            }
            // Make it possible to distinguish the individual on-click
            // action of a given item.
            setOnClickFillInIntent(R.id.widget_item, fillInIntent)
            ...
        }
    }
    ...
}

Java

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private static final int count = 10;
    private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>();
    private Context context;
    private int appWidgetId;

    public StackRemoteViewsFactory(Context context, Intent intent) {
        this.context = context;
        appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    // Initialize the data set.
    public void onCreate() {
        // In onCreate(), set up any connections or cursors to your data
        // source. Heavy lifting, such as downloading or creating
        // content, must be deferred to onDataSetChanged() or
        // getViewAt(). Taking more than 20 seconds on this call results
        // in an ANR.
        for (int i = 0; i < count; i++) {
            widgetItems.add(new WidgetItem(i + "!"));
        }
        ...
    }

    // Given the position (index) of a WidgetItem in the array, use the
    // item's text value in combination with the widget item XML file to
    // construct a RemoteViews object.
    public RemoteViews getViewAt(int position) {
        // Position always ranges from 0 to getCount() - 1.

        // Construct a RemoteViews item based on the widget item XML
        // file and set the text based on the position.
        RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
        rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text);

        // Set a fill-intent to fill in the pending
        // intent template that is set on the collection view in
        // StackWidgetProvider.
        Bundle extras = new Bundle();
        extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);
        Intent fillInIntent = new Intent();
        fillInIntent.putExtras(extras);
        // Make it possible to distinguish the individual on-click
        // action of a given item.
        rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);

        // Return the RemoteViews object.
        return rv;
    }
    ...
}

Keep collection data fresh

Figure 2 illustrates the update flow in a widget that uses collections. It shows how the widget code interacts with the RemoteViewsFactory and how you can trigger updates:

Figure 2. Interaction with RemoteViewsFactory during updates.

Widgets that use collections can provide users with up-to-date content. For example, the Gmail widget gives users a snapshot of their inbox. To make this possible, trigger your RemoteViewsFactory and collection view to fetch and display new data.

To do this, use the AppWidgetManager to call notifyAppWidgetViewDataChanged(). This call results in a callback to your RemoteViewsFactory object’s onDataSetChanged() method, which lets you fetch any new data.

You can perform processing-intensive operations synchronously within the onDataSetChanged() callback. You are guaranteed that this call is completed before the metadata or view data is fetched from the RemoteViewsFactory. You can also perform processing-intensive operations within the getViewAt() method. If this call takes long, the loading view—specified by the RemoteViewsFactory object's getLoadingView() method—is displayed in the corresponding position of the collection view until it returns.

Use RemoteCollectionItems to pass along a collection directly

Android 12 (API level 31) adds the setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) method, which lets your app pass along a collection directly when populating a collection view. If you set your adapter using this method, you don't need to implement a RemoteViewsFactory and you don't need to call notifyAppWidgetViewDataChanged().

In addition to making it easier to populate your adapter, this approach also removes the latency for populating new itemswhen users scroll down the list to reveal a new item. This approach to setting the adapter is preferred as long as your set of collection items is relatively small. However, for example, this approach doesn't work well if your collection contains numerous Bitmaps being passed to setImageViewBitmap.

If the collection doesn’t use a constant set of layouts—that is, if some items are only sometimes present—use setViewTypeCount to specify the maximum number of unique layouts that the collection can contain. This lets the adapter be reused across updates to your app widget.

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

Kotlin

val itemLayouts = listOf(
        R.layout.item_type_1,
        R.layout.item_type_2,
        ...
)

remoteView.setRemoteAdapter(
        R.id.list_view,
        RemoteViews.RemoteCollectionItems.Builder()
            .addItem(/* id= */ ID_1, RemoteViews(context.packageName, R.layout.item_type_1))
            .addItem(/* id= */ ID_2, RemoteViews(context.packageName, R.layout.item_type_2))
            ...
            .setViewTypeCount(itemLayouts.count())
            .build()
)

Java

List<Integer> itemLayouts = Arrays.asList(
    R.layout.item_type_1,
    R.layout.item_type_2,
    ...
);

remoteView.setRemoteAdapter(
    R.id.list_view,
    new RemoteViews.RemoteCollectionItems.Builder()
        .addItem(/* id= */ ID_1, new RemoteViews(context.getPackageName(), R.layout.item_type_1))
        .addItem(/* id= */ ID_2, new RemoteViews(context.getPackageName(), R.layout.item_type_2))
        ...
        .setViewTypeCount(itemLayouts.size())
        .build()
);