Collection widgets specialize in displaying many elements of the same type, such as a collection of pictures from a gallery app, a collection of articles from a news app or a collection of 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 for consumption. Collection widgets can scroll vertically.
These kinds of widgets use the RemoteViewsService
to display collections that are backed by remote data, such as from a content
provider. The data provided by the
presented in the widget using one of the following view types, which
we’ll refer to as collection views:
ListView
- A view that shows items in a vertically scrolling list.
GridView
- A view that shows items in two-dimensional scrolling grid.
StackView
- A stacked card view (kind of like a rolodex), where the user can flick the front card up/down to see the previous/next card, respectively.
AdapterViewFlipper
- An adapter-backed simple
ViewAnimator
that animates between two or more views. Only one child is shown at a time.
As stated previously in this document, these collection views display
collections backed by remote data. This means that they use an Adapter
to bind their user interface to their data. An Adapter
binds individual items
from a set of data into individual View
objects. 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 simply 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. In order to
include a collection view in your widget, you must implement
RemoteViewsService
and RemoteViewsFactory
.
RemoteViewsService
is a service that allows a remote adapter to 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 you use 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 drawn from the
StackWidget
sample:
This sample consists of a stack of 10 views, which display the values
"0"
through "9"
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 slide show. This is due to the setting
android:autoAdvanceViewId="@id/stack_view"
in theres/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 of how this is implemented, see Adding behavior to individual items.
Implement widgets with collections
To implement a widget with collections, follow the same 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
In addition to the requirements listed in Declaring a widget in the
manifest, to make it possible for widgets with collections
to bind to your RemoteViewsService
, you must declare 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 may look like this:
<service android:name="MyWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
The line 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, you must 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
only has one layout file, widget_item.xml
, since all items use the same
layout.
AppWidgetProvider
class for widgets with collections
As with a regular widget, the bulk of your 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, you must 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 will // provide 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 should 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 will // provide 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 should 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); }
Persisting data
As described previously in this document, RemoteViewsService
subclass
provides the RemoteViewsFactory
used to populate the remote collection view.
Specifically, you need to perform these steps:
Subclass
RemoteViewsService
.RemoteViewsService
is the service through which a remote adapter can requestRemoteViews
.In your
RemoteViewsService
subclass, include a class that implements theRemoteViewsFactory
interface.RemoteViewsFactory
is an interface for an adapter between a remote collection view (such asListView
,GridView
,StackView) and the underlying data for that view. Your implementation is responsible for making a
RemoteViewsobject for each item in the data set. 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. You should therefore not store any 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 could
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 could 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 and/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 the text they contain is displayed.
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() you setup any connections / cursors to your data // source. Heavy lifting, for example downloading or creating content // etc, should be deferred to onDataSetChanged() or getViewAt(). Taking // more than 20 seconds in this call will result 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() you setup any connections / cursors to your data // source. Heavy lifting, for example downloading or creating content // etc, should be deferred to onDataSetChanged() or getViewAt(). Taking // more than 20 seconds in this call will result 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 you 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 (to clarify, you could 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, you 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
(anAppWidgetProvider
subclass) creates a pending intent that has a custom action calledTOAST_ACTION
.When the user touches a view, the intent is fired and it broadcasts
TOAST_ACTION
.This broadcast is intercepted by the
StackWidgetProvider
'sonReceive()
method, and the widget displays theToast
message for the touched view. The data for the collection items is provided by theRemoteViewsFactory
, via theRemoteViewsService
.
Set up the pending intent template
The StackWidgetProvider
(AppWidgetProvider
subclass)
sets up a pending intent. Individual items of a collection cannot 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 to see 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 which // position of the item was clicked. 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 will // provide 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 we need to embed the extras // into the data so that the extras will not be 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 should 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 cannot 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 will have 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 to see 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 which // position of the item was clicked. 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 will // provide 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 we need to // embed the extras into the data so that the extras will not be // 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 // should 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 cannot 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 will have 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 in order to
determine the final intent that will be executed when the item is clicked.
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() you setup any connections / cursors to your data source. Heavy lifting, // for example downloading or creating content etc, should be deferred to onDataSetChanged() // or getViewAt(). Taking more than 20 seconds in this call will result 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) // Next, set a fill-intent, which will be used 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() you set up any connections / cursors to your data // source. Heavy lifting, for example downloading or creating // content etc, should be deferred to onDataSetChanged() or // getViewAt(). Taking more than 20 seconds in this call will result // 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 will always range 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); // Next, set a fill-intent, which will be used 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
The following figure illustrates the flow that occurs in a widget that uses
collections when updates occur. It shows how the widget code interacts with
the
RemoteViewsFactory
,
and how you can trigger updates:
One feature of widgets that use collections is the ability to provide users
with up-to-date content. For example, consider the Gmail widget, which provides
users with a snapshot of their inbox. To make this possible, you need to be able
to trigger your RemoteViewsFactory
and collection view to fetch and display
new data. You achieve this with the
AppWidgetManager
call
notifyAppWidgetViewDataChanged()
.
This call results in a callback to your RemoteViewsFactory
’s
onDataSetChanged()
method, which gives you the opportunity to fetch any new data. Note that you can
perform processing-intensive operations synchronously within the
onDataSetChanged()
callback. You are guaranteed that this call will be completed before the
metadata or view data is fetched from the RemoteViewsFactory
. In addition,
you can perform processing-intensive operations within the getViewAt()
method. If this call takes a long time, the loading view (specified by the
RemoteViewsFactory
object's getLoadingView()
method) will be 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 do not need to
implement a RemoteViewsFactory
and you do not need to call
notifyAppWidgetViewDataChanged()
. In addition to making it easier to populate
your adapter, this approach also removes the latency for populating new
items when 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 in size. 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 allows the
adapter to 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(R.layout.item_type_1)) .addItem(/* id= */ ID_2, RemoteViews(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(R.layout.item_type_1)) .addItem(/* id= */ ID_2, new RemoteViews(R.layout.item_type_2)) ... .setViewTypeCount(itemLayouts.size()) .build() );