Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Cómo crear un widget de la app

Los widgets son vistas de apps en miniatura que pueden incorporarse en otras apps (como la pantalla principal) y recibir actualizaciones periódicas. Estas vistas se denominan widgets en la interfaz de usuario, y puedes publicar una con un proveedor de widgets de apps. Se denomina host de widgets de la app el componente de esta que puede contener otros widgets de la app. En la siguiente captura de pantalla, se muestra el widget de la app de Música.

En este documento, se describe cómo publicar el widget de una app utilizando un proveedor de widgets de apps. Si deseas obtener más información sobre cómo crear tu propio AppWidgetHost para alojar widgets de apps, consulta Host de widgets de apps.

Nota: Para obtener información sobre cómo diseñar el widget de tu app, consulta Descripción general de los widgets de apps.

Lo básico

Para crear el widget de una app, necesitas lo siguiente:

Objeto AppWidgetProviderInfo
Describe los metadatos del widget de una app, como el diseño, la frecuencia de actualización y la clase AppWidgetProvider. Se debe definir en formato XML.
Implementación de la clase AppWidgetProvider
Define los métodos básicos que te permiten interactuar de manera programática con el widget de la app, en función de los eventos de emisión. De esta manera, recibirás emisiones cuando se actualice, se habilite, se inhabilite o se borre el widget de la app.
Diseño de la vista
Define el diseño inicial del widget de la app en formato XML.

Además, puedes implementar una actividad de configuración del widget de la app. Este es un Activity opcional que se inicia cuando el usuario agrega el widget de tu app y le permite modificar la configuración de este durante la creación.

En las siguientes secciones, se describe cómo configurar cada uno de estos componentes.

Cómo declarar el widget de una app en el manifiesto

Primero, declara la clase AppWidgetProvider en el archivo AndroidManifest.xml de tu app. Por ejemplo:

    <receiver android:name="ExampleAppWidgetProvider" >
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
                   android:resource="@xml/example_appwidget_info" />
    </receiver>
    

El elemento <receiver> requiere el atributo android:name, que especifica el AppWidgetProvider que utiliza el widget de la app.

El elemento <intent-filter> debe incluir un elemento <action> con el atributo android:name. Este atributo especifica que el AppWidgetProvider acepta la emisión de ACTION_APPWIDGET_UPDATE. Esta es la única emisión que debes declarar de manera explícita. El AppWidgetManager envía automáticamente todas las demás emisiones del widget de la app al AppWidgetProvider según sea necesario.

El elemento <meta-data> especifica el recurso AppWidgetProviderInfo y requiere los siguientes atributos:

  • android:name: Especifica el nombre de los metadatos. Usa android.appwidget.provider para identificar los datos como el descriptor AppWidgetProviderInfo.
  • android:resource: Especifica la ubicación del recurso AppWidgetProviderInfo.

Cómo agregar los metadatos de AppWidgetProviderInfo

El AppWidgetProviderInfo define las cualidades esenciales de un widget de app, como las dimensiones mínimas de diseño, el recurso de diseño inicial, la frecuencia con la que se actualiza el widget y (opcionalmente) una actividad de configuración para iniciar durante la creación. Define el objeto AppWidgetProviderInfo en un recurso XML utilizando un solo elemento <appwidget-provider> y guárdalo en la carpeta res/xml/ del proyecto.

Por ejemplo:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="40dp"
        android:minHeight="40dp"
        android:updatePeriodMillis="86400000"
        android:previewImage="@drawable/preview"
        android:initialLayout="@layout/example_appwidget"
        android:configure="com.example.android.ExampleAppWidgetConfigure"
        android:resizeMode="horizontal|vertical"
        android:widgetCategory="home_screen">
    </appwidget-provider>
    

A continuación, se muestra un resumen de los atributos <appwidget-provider>:

  • Los valores de los atributos minWidth y minHeight especifican la cantidad mínima de espacio que consume el widget de la app de forma predeterminada. En la pantalla principal predeterminada, se colocan los widgets de las apps en la ventana según una cuadrícula de celdas que tienen una altura y un ancho definidos. Si los valores de altura o ancho mínimos del widget de una app no coinciden con las dimensiones de las celdas, el tamaño del widget de la app se redondea hacia arriba según el tamaño de la celda más cercana.

    Consulta las Pautas de diseño de widgets de apps para obtener más información sobre el tamaño de tus widgets.

    Nota: Para que el widget de la app sea portátil en todos los dispositivos, debe tener un tamaño mínimo que nunca sea superior a 4 x 4 celdas.

  • Los atributos minResizeWidth y minResizeHeight especifican el tamaño mínimo absoluto del widget de la app. Estos valores deben especificar el tamaño mínimo por debajo del cual el widget de la app sería ilegible o inutilizable. El uso de estos atributos permite al usuario cambiar el tamaño del widget por un tamaño que puede ser menor que el predeterminado, definido por los atributos minWidth y minHeight. Esta opción se introdujo en Android 3.1.

    Consulta las Pautas de diseño de widgets de apps para obtener más información sobre el tamaño de tus widgets.

  • El atributo updatePeriodMillis define la frecuencia con la que el marco de trabajo del widget de la app debe solicitar una actualización desde el AppWidgetProvider llamando al método de devolución de llamada onUpdate(). Con este valor, no se garantiza que la actualización real ocurra exactamente a tiempo, y sugerimos realizarla con la menor frecuencia posible (tal vez no más de una por hora para conservar la batería). También puedes permitir que el usuario ajuste la frecuencia en una configuración; es posible que algunos usuarios quieran que un código bursátil se actualice cada 15 minutos, o tal vez solo cuatro veces al día.

    Nota: Si el dispositivo está inactivo en el momento de una actualización (según lo define updatePeriodMillis), se activará para instalarla. Si no actualizas más de una vez por hora, es probable que esto no produzca inconvenientes graves en relación con la duración de la batería. Sin embargo, si necesitas actualizar con más frecuencia o no necesitas instalar actualizaciones cuando el dispositivo está inactivo, puedes instalarlas en función de una alarma que no lo active. Para hacerlo, configura una alarma con un intent que reciba el AppWidgetProvider, usando el AlarmManager. Configura una alarma de tipo ELAPSED_REALTIME o RTC, que solo se reproduzca cuando el dispositivo esté activo. Luego, establece updatePeriodMillis en cero ("0").

  • El atributo initialLayout está orientado al recurso de diseño que define el diseño del widget de la app.
  • El atributo configure define el Activity que se iniciará cuando el usuario agregue el widget de la app, a fin de que pueda configurar las propiedades del widget. Esto es opcional (lee Cómo crear una actividad de configuración del widget de una app a continuación).
  • El atributo previewImage especifica una vista previa del aspecto del widget de la app después de la configuración, es decir, lo que el usuario ve cuando selecciona el widget. Si no se proporciona este atributo, el usuario ve el ícono del selector de tu app. Este campo corresponde al atributo android:previewImage del elemento <receiver> del archivo AndroidManifest.xml. Para obtener más información sobre el uso de previewImage, consulta Cómo configurar una imagen de vista previa. Esta opción se introdujo en Android 3.0.
  • El atributo autoAdvanceViewId especifica el ID de vista de la subvista del widget de la app, que el host debe hacer avanzar automáticamente. Esta opción se introdujo en Android 3.0.
  • El atributo resizeMode especifica las reglas por las que se puede cambiar el tamaño de un widget. Usa este atributo para hacer que se pueda cambiar el tamaño de los widgets de la pantalla principal de forma horizontal o vertical, o en ambos ejes. Los usuarios deben mantener presionado un widget para mostrar los controladores de cambio de tamaño y, luego, arrastrar los controladores de forma horizontal y/o vertical para cambiar el tamaño en la cuadrícula de diseño. Los valores para el atributo resizeMode incluyen "horizontal", "vertical" y "none". Para declarar un widget como horizontal y vertical, proporciona el valor "horizontal|vertical". Esta opción se introdujo en Android 3.1.
  • El atributo minResizeHeight especifica la altura mínima (en dps) a la que se puede cambiar el tamaño del widget. Este campo no tiene efecto si es mayor que minHeight o si el cambio de tamaño vertical no está habilitado (consulta resizeMode). Esta opción se introdujo en Android 4.0.
  • El atributo minResizeWidth especifica el ancho mínimo (en dps) al que se puede cambiar el tamaño del widget. Este campo no tiene efecto si es mayor que minWidth o si el cambio de tamaño horizontal no está habilitado (consulta resizeMode). Esta opción se introdujo en Android 4.0.
  • El atributo widgetCategory declara si el widget de tu app se puede mostrar en la pantalla principal (home_screen), la pantalla de bloqueo (keyguard) o ambas. Solo las versiones de Android anteriores a 5.0 admiten widgets de pantalla de bloqueo. Para Android 5.0 y versiones posteriores, solo es válido home_screen.

Consulta la clase AppWidgetProviderInfo para obtener más información sobre los atributos que acepta el elemento <appwidget-provider>.

Cómo crear el diseño del widget de la app

Debes definir un diseño inicial para el widget de tu app en formato XML y guardarlo en el directorio res/layout/ del proyecto. Puedes diseñar el widget de tu app utilizando los objetos de vista que se enumeran a continuación; pero, antes de comenzar a diseñar el widget, debes leer y comprender las Pautas de diseño de widgets de apps.

Crear el diseño del widget de la app te resultará simple si tienes conocimientos sobre los diseños. Sin embargo, debes tener en cuenta que los diseños de los widgets de las apps se basan en RemoteViews, que no son compatibles con todo tipo de diseño o widget de vista.

Un objeto RemoteViews (y, en consecuencia, el widget de una app) puede admitir las siguientes clases de diseño:

Y las siguientes clases de widgets:

No son compatibles los elementos descendientes de estas clases.

RemoteViews también admite ViewStub, una vista invisible de tamaño cero que puedes usar para ampliar de manera diferida los recursos de diseño durante el tiempo de ejecución.

Cómo agregar márgenes a los widgets de apps

Por lo general, los widgets no deben extenderse hasta los bordes de la pantalla y no deben estar alineados con otros widgets. Por lo tanto, debes agregar márgenes alrededor del marco del widget.

A partir de Android 4.0, se coloca automáticamente un relleno entre el marco y el cuadro de límite del widget de la app a fin de proporcionar una mejor alineación con otros widgets e íconos en la pantalla principal del usuario. Para aprovechar las ventajas de este comportamiento recomendado, establece la targetSdkVersion de la app como 14 o superior.

Es fácil escribir un solo diseño que tenga márgenes personalizados aplicados para versiones anteriores de la plataforma y que no tenga márgenes adicionales para Android 4.0 y versiones posteriores:

  1. Establece la targetSdkVersion de tu app como 14 o superior.
  2. Crea un diseño como el siguiente, que haga referencia a un recurso de dimensión para sus márgenes:
        <FrameLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:padding="@dimen/widget_margin">
    
          <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:background="@drawable/my_widget_background">
            …
          </LinearLayout>
    
        </FrameLayout>
        
  3. Crea recursos de dos dimensiones, uno en res/values/ a fin de proporcionar los márgenes personalizados para versiones anteriores a Android 4.0 y uno en res/values-v14/ a fin de no proporcionar relleno adicional para los widgets de Android 4.0:

    res/values/dimens.xml:

    <dimen name="widget_margin">8dp</dimen>

    res/values-v14/dimens.xml:

    <dimen name="widget_margin">0dp</dimen>

Otra opción es solo crear márgenes adicionales en tus elementos de fondo de 9-Patch de forma predeterminada y proporcionar diferentes elementos de 9-Patch sin márgenes para la API nivel 14 o superior.

Cómo usar la clase AppWidgetProvider

La clase AppWidgetProvider extiende BroadcastReceiver como una clase de conveniencia para manejar las emisiones del widget de la app. AppWidgetProvider recibe solo las emisiones de eventos que son relevantes para el widget de la app, como cuando este se actualiza, se borra, se habilita o se deshabilita. Cuando se producen estos eventos de emisión, AppWidgetProvider recibe las siguientes llamadas a métodos:

onUpdate()
Se llama a este método para actualizar el widget de la app a intervalos definidos por el atributo updatePeriodMillis en AppWidgetProviderInfo (consulta Cómo agregar los metadatos de AppWidgetProviderInfo más arriba). También se llama a este método cuando el usuario agrega el widget de la app, por lo que debe realizar la configuración esencial, como definir controladores de eventos para vistas e iniciar un Service temporal, si es necesario. Sin embargo, si declaraste una actividad de configuración, no se llama a este método cuando el usuario agrega el widget de la app, sino para las actualizaciones posteriores. Es responsabilidad de la actividad de configuración realizar la primera actualización durante la configuración. (Consulta Cómo crear una actividad de configuración del widget de una app a continuación).
onAppWidgetOptionsChanged()
Se llama a este método cuando se coloca por primera vez el widget y cada vez que se le cambia el tamaño. Puedes usar esta devolución de llamada para mostrar u ocultar contenido en función de los rangos de tamaño del widget. Para obtener los rangos de tamaño, llama a getAppWidgetOptions(), que mostrará un Bundle que incluye lo siguiente:

Esta devolución de llamada se introdujo en la API nivel 16 (Android 4.1). Si implementas esta devolución de llamada, asegúrate de que tu app no dependa de ella, ya que no se llamará en dispositivos más antiguos.
onDeleted(Context, int[])
Se llama a este método cada vez que se borra el widget de una app del host de widgets.
onEnabled(Context)
Se llama a este método cuando se crea por primera vez una instancia de widget de una app. Por ejemplo, si el usuario agrega dos instancias del widget de tu app, solo se lo llama la primera vez. Si necesitas abrir una nueva base de datos o realizar otra configuración única para todas las instancias del widget de la app, este es un buen lugar para hacerlo.
onDisabled(Context)
Se llama a este método cuando se borra la última instancia del widget de tu app desde el host de widgets. Aquí debes limpiar los trabajos realizados en onEnabled(Context), como borrar una base de datos temporal.
onReceive(Context, Intent)
Se llama a este método para cada emisión y antes de cada uno de los métodos de devolución de llamada que se mencionan arriba. Por lo general, no debes implementar este método porque la implementación predeterminada de AppWidgetProvider filtra todas las emisiones del widget de la app y llama a los métodos anteriores según corresponda.

Debes declarar tu implementación de la clase AppWidgetProvider como receptor de emisión con el elemento <receiver> en AndroidManifest (consulta Cómo declarar el widget de una app en el manifiesto más arriba).

La devolución de llamada de AppWidgetProvider más importante es onUpdate() porque se la llama cuando se agrega cada widget de app a un host (a menos que uses una actividad de configuración). Si el widget de la app acepta eventos de interacción con el usuario, debes registrar los controladores de eventos en esta devolución de llamada. Si el widget de tu app no crea archivos o bases de datos temporales, o no realiza otro trabajo que requiera limpieza, onUpdate() puede ser el único método de devolución de llamada que debas definir. Por ejemplo, si quieres que el widget de una app tenga un botón que inicie una actividad cuando se haga clic en él, puedes usar la siguiente implementación de AppWidgetProvider:

Kotlin

    class ExampleAppWidgetProvider : AppWidgetProvider() {

        override fun onUpdate(
                context: Context,
                appWidgetManager: AppWidgetManager,
                appWidgetIds: IntArray
        ) {
            // Perform this loop procedure for each App Widget that belongs to this provider
            appWidgetIds.forEach { appWidgetId ->
                // Create an Intent to launch ExampleActivity
                val pendingIntent: PendingIntent = Intent(context, ExampleActivity::class.java)
                        .let { intent ->
                            PendingIntent.getActivity(context, 0, intent, 0)
                        }

                // Get the layout for the App Widget and attach an on-click listener
                // to the button
                val views: RemoteViews = RemoteViews(
                        context.packageName,
                        R.layout.appwidget_provider_layout
                ).apply {
                    setOnClickPendingIntent(R.id.button, pendingIntent)
                }

                // Tell the AppWidgetManager to perform an update on the current app widget
                appWidgetManager.updateAppWidget(appWidgetId, views)
            }
        }
    }
    

Java

    public class ExampleAppWidgetProvider extends AppWidgetProvider {

        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            final int N = appWidgetIds.length;

            // Perform this loop procedure for each App Widget that belongs to this provider
            for (int i=0; i<N; i++) {
                int appWidgetId = appWidgetIds[i];

                // Create an Intent to launch ExampleActivity
                Intent intent = new Intent(context, ExampleActivity.class);
                PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

                // Get the layout for the App Widget and attach an on-click listener
                // to the button
                RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
                views.setOnClickPendingIntent(R.id.button, pendingIntent);

                // Tell the AppWidgetManager to perform an update on the current app widget
                appWidgetManager.updateAppWidget(appWidgetId, views);
            }
        }
    }
    

Este AppWidgetProvider define solamente el método onUpdate() con el objetivo de definir un PendingIntent que inicie un Activity y de adjuntarlo al botón del widget de la app con setOnClickPendingIntent(int, PendingIntent). Ten en cuenta que incluye un bucle que se repite en cada entrada en appWidgetIds, que es un arreglo de ID que identifica cada widget de app creado por este proveedor. De esta manera, si el usuario crea más de una instancia del widget de la app, se actualizan todos de forma simultánea. Sin embargo, solo se administrará una programación de updatePeriodMillis para todas las instancias del widget de la app. Por ejemplo, si se define el programa de actualización cada dos horas y se agrega una segunda instancia del widget una hora después de la primera, se actualizarán ambos en el período definido por la primera, y se ignorará el período de la segunda actualización (ambos se actualizarán cada dos horas, no cada una hora).

Nota: Debido a que AppWidgetProvider es una extensión de BroadcastReceiver, no se garantiza que tu proceso siga funcionando después de los métodos de devolución de llamada (consulta BroadcastReceiver para obtener información sobre el ciclo de vida de emisión). Si el proceso de configuración del widget de tu app puede tardar varios segundos (tal vez cuando se realizan solicitudes web) y quieres que el proceso continúe, considera iniciar un Service en el método onUpdate(). Desde dentro del servicio, puedes realizar tus propias actualizaciones del widget de la app sin preocuparte por si se cierra el AppWidgetProvider debido a un error del tipo Aplicación no responde (ANR). Consulta el AppWidgetProvider de la muestra de Wiktionary para obtener un ejemplo de un widget de una app que ejecuta un Service.

Consulta también la clase de muestra ExampleAppWidgetProvider.java.

Cómo recibir intents de emisión del widget de una app

AppWidgetProvider es solo una clase de conveniencia. Si deseas recibir directamente las emisiones del widget de la app, puedes implementar tu propio BroadcastReceiver o anular la devolución de llamada onReceive(Context, Intent). Los intents a los que debes prestar atención son los siguientes:

Cómo fijar widgets de apps

En los dispositivos con Android 8.0 (API nivel 26) o posterior, los selectores que te permiten crear accesos directos fijos también te permiten fijar widgets de apps. Al igual que los accesos directos fijos, estos widgets fijos ofrecen a los usuarios acceso a tareas específicas de tu app.

En tu app, puedes crear una solicitud para que el sistema fije un widget en un selector compatible completando la siguiente secuencia de pasos:

  1. Crea el widget en el archivo de manifiesto de tu app, como se muestra en el siguiente fragmento:
        <manifest>
        ...
          <application>
            ...
            <receiver android:name="MyAppWidgetProvider">
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
                <meta-data android:name="android.appwidget.provider"
                           android:resource="@xml/my_appwidget_info" />
            </receiver>
          </application>
        </manifest>
        
  2. Llama al método requestPinAppWidget(), como se muestra en el siguiente fragmento de código:

    Kotlin

        val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
        val myProvider = ComponentName(context, MyAppWidgetProvider::class.java)
    
        val successCallback: PendingIntent? = if (appWidgetManager.isRequestPinAppWidgetSupported) {
            // Create the PendingIntent object only if your app needs to be notified
            // that the user allowed the widget to be pinned. Note that, if the pinning
            // operation fails, your app isn't notified.
            Intent(...).let { intent ->
                // Configure the intent so that your app's broadcast receiver gets
                // the callback successfully. This callback receives the ID of the
                // newly-pinned widget (EXTRA_APPWIDGET_ID).
                PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
            }
        } else {
            null
        }
    
        successCallback?.also { pendingIntent ->
            appWidgetManager.requestPinAppWidget(myProvider, null, pendingIntent)
        }
        

    Java

        AppWidgetManager appWidgetManager =
                context.getSystemService(AppWidgetManager.class);
        ComponentName myProvider =
                new ComponentName(context, MyAppWidgetProvider.class);
    
        if (appWidgetManager.isRequestPinAppWidgetSupported()) {
            // Create the PendingIntent object only if your app needs to be notified
            // that the user allowed the widget to be pinned. Note that, if the pinning
            // operation fails, your app isn't notified.
            Intent pinnedWidgetCallbackIntent = new Intent( ... );
    
            // Configure the intent so that your app's broadcast receiver gets
            // the callback successfully. This callback receives the ID of the
            // newly-pinned widget (EXTRA_APPWIDGET_ID).
            PendingIntent successCallback = PendingIntent.getBroadcast(context, 0,
                    pinnedWidgetCallbackIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            appWidgetManager.requestPinAppWidget(myProvider, null, successCallback);
        }
        

Nota: Si no es necesario que tu app reciba una notificación cuando el sistema fijó correctamente un widget en un selector compatible, puedes pasar null como tercer argumento a requestPinAppWidget().

Cómo crear una actividad de configuración del widget de una app

Si deseas que el usuario ajuste la configuración cuando agregue un nuevo widget, puedes crear una actividad de configuración del widget de la app. El host de widget de apps iniciará automáticamente este Activity y permitirá al usuario configurar el widget de la app durante la creación. Por ejemplo, podrá establecer el color, el tamaño, el período de actualización y otras configuraciones de la app.

Se debe declarar la actividad de configuración como una actividad normal en el archivo de manifiesto de Android. Sin embargo, el host de widgets de apps lo iniciará con la acción ACTION_APPWIDGET_CONFIGURE, por lo que la actividad debe aceptar este intent. Por ejemplo:

    <activity android:name=".ExampleAppWidgetConfigure">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
        </intent-filter>
    </activity>
    

Además, se debe declarar la actividad en el archivo XML AppWidgetProviderInfo, con el atributo android:configure (consulta Cómo agregar los metadatos de AppWidgetProviderInfo más arriba). Por ejemplo, se puede declarar la actividad de configuración de la siguiente manera:

    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        ...
        android:configure="com.example.android.ExampleAppWidgetConfigure"
        ... >
    </appwidget-provider>
    

Ten en cuenta que se declara la actividad con un espacio de nombres totalmente calificado, ya que se hará referencia desde fuera del alcance de tu paquete.

Eso es todo lo que necesitas para comenzar una actividad de configuración. Ahora, solo necesitas la actividad real. Sin embargo, debes recordar dos aspectos importantes cuando implementas la actividad:

  • El host de widgets de apps llama a la actividad de configuración, y esta siempre debe mostrar un resultado. El resultado debe incluir el ID del widget de la app que pasó el intent que inició la actividad (en los extras del intent, se guardó como EXTRA_APPWIDGET_ID).
  • No se llamará al método onUpdate() cuando se cree el widget de la app (el sistema no enviará la emisión ACTION_APPWIDGET_UPDATE cuando se inicie una actividad de configuración). Es responsabilidad de la actividad de configuración solicitar una actualización desde AppWidgetManager cuando se crea el widget de la app por primera vez. Sin embargo, se llamará a onUpdate() para actualizaciones posteriores (solo se omitirá la primera vez).

Consulta los fragmentos de código de la siguiente sección para conocer un ejemplo de cómo mostrar un resultado de la configuración y actualizar el widget de la app.

Cómo actualizar el widget de la app desde la actividad de configuración

Cuando el widget de una app utiliza una actividad de configuración, es responsabilidad de la actividad actualizar el widget cuando se completa la configuración. Puedes hacerlo solicitando una actualización directamente desde AppWidgetManager.

A continuación, encontrarás un resumen del procedimiento que debes seguir para actualizar correctamente el widget de la app y cerrar la actividad de configuración:

  1. Primero, obtén el ID del widget de la app desde el intent que inició la actividad:

    Kotlin

        appWidgetId = intent?.extras?.getInt(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID
        ) ?: AppWidgetManager.INVALID_APPWIDGET_ID
        

    Java

        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            appWidgetId = extras.getInt(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }
        
  2. Configura el widget de tu app.
  3. Cuando termines de configurarlo, obtén una instancia del AppWidgetManager llamando a getInstance(Context):

    Kotlin

        val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
        

    Java

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        
  4. Actualiza el widget de la app con un diseño de RemoteViews llamando al updateAppWidget(int, RemoteViews):

    Kotlin

        RemoteViews(context.packageName, R.layout.example_appwidget).also { views->
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
        

    Java

        RemoteViews views = new RemoteViews(context.getPackageName(),
        R.layout.example_appwidget);
        appWidgetManager.updateAppWidget(appWidgetId, views);
        
  5. Por último, crea el intent de retorno, configúralo con el resultado de la actividad y finaliza la actividad:

    Kotlin

        val resultValue = Intent().apply {
            putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
        }
        setResult(Activity.RESULT_OK, resultValue)
        finish()
        

    Java

        Intent resultValue = new Intent();
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        setResult(RESULT_OK, resultValue);
        finish();
        

Sugerencia: Cuando se abra por primera vez la actividad de configuración, establece el resultado como RESULT_CANCELED, junto con EXTRA_APPWIDGET_ID, como se muestra arriba, en el paso 5. De esta manera, si el usuario cancela la actividad antes del final, se notifica al host de widgets de apps que se canceló la configuración y que no se agregará el widget de la app.

Consulta la clase de muestra ExampleAppWidgetConfigure.java en ApiDemos para ver un ejemplo.

Cómo configurar una imagen de vista previa

En Android 3.0, se introdujo el campo previewImage, que especifica una vista previa del aspecto del widget de la app. El usuario puede ver esta vista previa desde el selector de widgets. Si no se proporciona este campo, se usa el ícono del widget de la app para la vista previa.

Debes especificar esta configuración en XML de la siguiente manera:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
      ...
      android:previewImage="@drawable/preview">
    </appwidget-provider>

Si quieres crear una imagen de vista previa para el widget de tu app (para especificar en el campo previewImage), el emulador de Android incluye una app llamada "Widget Preview". Para crear una imagen de vista previa, inicia esta app, selecciona el widget de tu app y configura el aspecto que deseas para la imagen de vista previa. Luego, guárdalo y colócala en los recursos de elementos de diseño de tu app.

Cómo usar widgets de apps con colecciones

Android 3.0 incluye widgets de apps con colecciones. Estos tipos de widgets de apps usan el RemoteViewsService para mostrar colecciones que están respaldadas por datos remotos, como las de un proveedor de contenido. Los datos proporcionados por el RemoteViewsService se presentan en el widget de la app mediante uno de los siguientes tipos de vista, a los que nos referiremos como "vistas de colección":

ListView
Una vista que muestra elementos en una lista de desplazamiento vertical. Para ver un ejemplo, consulta el widget de la app de Gmail.
GridView
Una vista que muestra elementos en una cuadrícula de desplazamiento bidimensional. Para ver un ejemplo, consulta el widget de la app de Marcadores.
StackView
Una vista de tarjetas apiladas (una especie de fichero), donde el usuario puede mover la tarjeta frontal hacia arriba o abajo para ver la tarjeta anterior o la siguiente. Entre los ejemplos, se incluyen los widgets de las apps de YouTube y Libros.
AdapterViewFlipper
Un ViewAnimator simple respaldado por adaptador que reproduce una animación entre dos o más vistas. Solo se muestra un elemento secundario por vez.

Como se indicó arriba, estas vistas de colección muestran colecciones respaldadas por datos remotos. Esto significa que usan un Adapter para vincular su interfaz de usuario con sus datos. Un Adapter vincula elementos individuales de un conjunto de datos en objetos View individuales. Debido a que estas vistas de colección están respaldadas por adaptadores, el marco de trabajo de Android debe incluir arquitectura adicional para admitir su uso en los widgets de las apps. En el contexto de un widget de una app, se reemplaza el Adapter por un RemoteViewsFactory, que es un wrapper liviano que rodea la interfaz del Adapter. Cuando se solicita un elemento específico de la colección, RemoteViewsFactory crea y muestra el artículo a la colección como un objeto RemoteViews. Para incluir una vista de colección en el widget de tu app, debes implementar RemoteViewsService y RemoteViewsFactory.

RemoteViewsService es un servicio que permite que un adaptador remoto solicite objetos RemoteViews. RemoteViewsFactory es una interfaz para un adaptador entre una vista de colección (como ListView, GridView, etc.) y los datos subyacentes para esa vista. De la muestra de StackWidget, aquí puedes ver un ejemplo del código repetitivo que usas para implementar este servicio y esta interfaz:

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 {

    //... include adapter-like methods here. See the StackView Widget sample.

    }
    

Java

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

    class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

    //... include adapter-like methods here. See the StackView Widget sample.

    }
    

App de muestra

Los extractos de código que se incluyen en esta sección se tomaron de la muestra de StackWidget:

Este ejemplo consta de una pila de 10 vistas, que muestran los valores "0!" a "9!". El widget de la app de muestra tiene estos comportamientos principales:

  • El usuario puede desplazar de forma vertical la vista superior en el widget de la app para mostrar la vista siguiente o anterior. Este es un comportamiento integrado de StackView.
  • Sin interacción del usuario, el widget de la app avanza automáticamente por sus vistas en una secuencia, como una presentación de diapositivas. Esto se debe a la configuración android:autoAdvanceViewId="@id/stack_view" en el archivo res/xml/stackwidgetinfo.xml. Esta configuración se aplica al ID de vistas que, en este caso, es el ID la vista de pila.
  • Si el usuario presiona la vista superior, el widget de la app muestra el mensaje Toast "Vista presionada n", donde n es el índice (posición) de la vista que se presionó. Para obtener más información sobre cómo se implementa esto, consulta Cómo agregar comportamientos a elementos individuales .

Cómo implementar widgets de apps con colecciones

Para implementar el widget de una app con colecciones, sigue los mismos pasos básicos que seguirías para implementar el widget de cualquier app. En las siguientes secciones, se describen los pasos adicionales que debes seguir para implementar el widget de una app con colecciones.

Manifiesto para widgets de apps con colecciones

Además de los requisitos que se enumeran en Cómo declarar el widget de una app en el manifiesto, para que los widgets de las apps con colecciones puedan vincularse con tu RemoteViewsService, debes declarar el servicio en el archivo del manifiesto con el permiso BIND_REMOTEVIEWS. Esto evita que otras apps accedan libremente a los datos del widget de tu app. Por ejemplo, cuando crees el widget de una app que use RemoteViewsService para propagar una vista de colección, es posible que la entrada del manifiesto tenga este aspecto:

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

La línea android:name="MyWidgetService" hace referencia a tu subclase de RemoteViewsService.

Diseño de widgets de apps con colecciones

El principal requisito para el archivo XML de diseño de widgets de apps es que incluya una de las vistas de colección: ListView, GridView, StackView o AdapterViewFlipper. Este es el widget_layout.xml para la muestra de StackWidget :

<?xml version="1.0" encoding="utf-8"?>

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <StackView xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/stack_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:loopViews="true" />
        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            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>

Ten en cuenta que las vistas vacías deben ser equivalentes a la vista de colección para la que la vista vacía representa el estado vacío.

Además del archivo de diseño para todo el widget de la app, debes crear otro archivo de diseño que defina el diseño de cada elemento de la colección (por ejemplo, un diseño para cada libro de una colección de libros). La muestra de StackWidget solo tiene un archivo de diseño, widget_item.xml, ya que todos los elementos usan el mismo diseño.

Clase AppWidgetProvider para widgets de apps con colecciones

Al igual que en los widgets de apps normales, por lo general, la mayor parte del código de tu subclase AppWidgetProvider corresponde a onUpdate(). La mayor diferencia en la implementación de onUpdate() en el momento de crear el widget de una app con colecciones es que debes llamar a setRemoteAdapter(). Este indica a la vista de colección dónde debe obtener los datos. Luego, el RemoteViewsService puede mostrar tu implementación de RemoteViewsFactory y el widget puede entregar los datos adecuados. Cuando llames a este método, deberás pasar un intent que esté orientado a tu implementación de RemoteViewsService y el ID del widget de la app que especifica qué widget se debe actualizar.

Por ejemplo, a continuación, incluye un ejemplo de cómo la muestra de StackWidget implementa el método de devolución de llamada onUpdate() para establecer el RemoteViewsService como el adaptador remoto para la colección del widget de la app:

Kotlin

    override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
    ) {
        // update each of the app 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 app 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 app widget layout.
            val rv = 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 above.
                setEmptyView(R.id.stack_view, R.id.empty_view)
            }

            //
            // Do additional processing specific to this app widget...
            //

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

Java

    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    int[] appWidgetIds) {
        // update each of the app 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 app 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 app widget layout.
            RemoteViews rv = 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.
            rv.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 above.
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);

            //
            // Do additional processing specific to this app widget...
            //

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

Clase RemoteViewsService

Datos persistentes

Como se describió anteriormente, la subclase RemoteViewsService proporciona la RemoteViewsFactory que se utiliza para propagar la vista de colección remota.

En especial, debes seguir estos pasos:

  1. Subclase RemoteViewsService. RemoteViewsService es el servicio mediante el cual un adaptador remoto puede solicitar RemoteViews.
  2. En tu subclase RemoteViewsService, incluye una clase que implemente la interfaz de RemoteViewsFactory. RemoteViewsFactory es una interfaz para un adaptador entre una vista de colección remota (como ListView, GridView, etc.) y los datos subyacentes de esa vista. La implementación es responsable de crear un objeto RemoteViews para cada elemento del conjunto de datos. Esta interfaz es un wrapper liviano que rodea a Adapter.

No puedes basarte en una sola instancia del servicio, ni en los datos que contiene, para que sea persistente. Por lo tanto, no debes almacenar ningún dato en tu RemoteViewsService (a menos que sea estático). Si deseas que los datos del widget de tu app sean persistentes, el mejor enfoque es utilizar un ContentProvider cuyos datos sean persistentes más allá del ciclo de vida del proceso.

El contenido principal de la implementación de RemoteViewsService es su RemoteViewsFactory, que se describe a continuación.

Interfaz de RemoteViewsFactory

La clase personalizada que implementa la interfaz de RemoteViewsFactory proporciona al widget de la app los datos para los elementos de su colección. Para lograrlo, combina el archivo de diseño XML de elementos del widget de tu app con una fuente de datos. Esta fuente de datos puede ser desde una base de datos a un simple arreglo. En la muestra de StackWidget, la fuente de datos es un arreglo de WidgetItems. RemoteViewsFactory funciona como un adaptador para unir los datos a la vista de colección remota.

Los dos métodos más importantes que debes implementar para la subclase RemoteViewsFactory son onCreate() y getViewAt().

El sistema llama a onCreate() cuando crea tu fábrica por primera vez. Aquí debes configurar las conexiones y/o los cursores para tu fuente de datos. Por ejemplo, la muestra de StackWidget usa onCreate() para inicializar un arreglo de objetos WidgetItem. Cuando el widget de tu app está activo, el sistema accede a estos objetos utilizando la posición de índice en el arreglo y se muestra el texto que contienen.

Este es un extracto de la implementación de RemoteViewsFactory de la muestra de StackWidget donde aparecen partes del método onCreate():

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 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);
        }

        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 < count; i++) {
                widgetItems.add(new WidgetItem(i + "!"));
            }
            ...
        }
    ...
    

El elemento getViewAt() del método RemoteViewsFactory muestra un objeto RemoteViews correspondiente a los datos en el position especificado en el conjunto de datos. A continuación, se incluye un extracto de la implementación de RemoteViewsFactory de la muestra de StackWidget:

Kotlin

    override fun getViewAt(position: Int): RemoteViews {
        // Construct a remote views item based on the app 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 app 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);

        ...
        // Return the remote views object.
        return rv;
    }
    

Cómo agregar comportamientos a elementos individuales

En las secciones anteriores, se muestra cómo vincular tus datos con la colección de widgets de tu app. Pero ¿qué sucede si deseas agregar un comportamiento dinámico a los elementos individuales de la vista de tu colección?

Como se describe en Cómo usar la clase AppWidgetProvider, por lo general, se usa setOnClickPendingIntent() para establecer el comportamiento de un objeto, como hacer que un botón inicie una Activity. Sin embargo, este enfoque no está permitido para las vistas secundarias de un elemento de colección individual (a modo de aclaración, por ejemplo, puedes usar setOnClickPendingIntent() para configurar un botón global en el widget de la app de Gmail que inicie la app, pero no en la lista de artículos individuales). En cambio, para agregar un comportamiento de clic a elementos individuales de una colección, utiliza setOnClickFillInIntent(). Esto implica configurar una plantilla de intents pendientes para la vista de colección y, luego, establecer un intent de relleno en cada elemento de la colección mediante el RemoteViewsFactory.

En esta sección, se utiliza la muestra de StackWidget para describir el modo en que se pueden agregar comportamientos a elementos individuales. En la muestra de StackWidget, si el usuario presiona la vista superior, el widget de la app muestra el mensaje Toast "Vista presionada n", donde n es el índice (posición) de la vista que se presionó. Funciona de la siguiente manera:

  • El StackWidgetProvider (una subclase AppWidgetProvider) crea un intent pendiente que tiene una acción personalizada llamada TOAST_ACTION.
  • Cuando el usuario presiona una vista, el intent se activa y transmite TOAST_ACTION.
  • El método onReceive() de StackWidgetProvider intercepta esta emisión y el widget de la app muestra el mensaje Toast para la vista que se presionó. RemoteViewsFactory proporciona los datos para los elementos de la colección mediante el RemoteViewsService.

Nota: La muestra de StackWidget usa una emisión, pero, normalmente, el widget de una app solo inicia una actividad en un escenario como este.

Cómo configurar la plantilla de intents pendientes

El StackWidgetProvider (subclase AppWidgetProvider) establece un intent pendiente. Los elementos individuales de una colección no pueden configurar sus propios intents pendientes. En cambio, la colección en su conjunto establece una plantilla de intents pendientes y los elementos individuales establecen un intent de relleno para crear un comportamiento único para cada elemento.

Esta clase también recibe la emisión que se envía cuando el usuario presiona una vista. Procesa este evento en el método onReceive(). Si la acción del intent es TOAST_ACTION, el widget de la app muestra un mensaje Toast para la vista actual.

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 app 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
                )
                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 app 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 app 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);
                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 app 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);
        }
    }
    
Cómo configurar el intent de relleno

Tu RemoteViewsFactory debe establecer un intent de relleno en cada elemento de la colección. Esto permite distinguir la acción individual que se lleva a cabo cuando se hace clic en un elemento determinado. El intent de relleno se combina con la plantilla PendingIntent para determinar el intent final que se ejecutará cuando se haga clic en el elemento.

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 app 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 app 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 app 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;
            }
        ...
        }
    

Cómo mantener actualizados los datos de las colecciones

En la siguiente figura, se ilustra el flujo que se produce en el widget de una app que usa colecciones cuando se realizan actualizaciones. Se muestra cómo interactúa el código del widget de la app con RemoteViewsFactory y cómo puedes activar las actualizaciones:

Una función de los widgets de apps que usan colecciones es la capacidad de proporcionar a los usuarios contenidos actualizados. Por ejemplo, piensa en el widget de la app de Gmail de Android 3.0, que ofrece una instantánea de la carpeta Recibidos. Para poder hacer esto, debes poder activar tu RemoteViewsFactory y la vista de colecciones de modo que obtengan y muestren datos nuevos. Esto se logra haciendo que AppWidgetManager llame a notifyAppWidgetViewDataChanged(). Esta llamada genera una devolución de llamada al método onDataSetChanged() de tu RemoteViewsFactory, lo que te da la oportunidad de obtener datos nuevos. Ten en cuenta que puedes realizar operaciones de procesamiento intensivo de manera síncrona dentro de la devolución de llamada onDataSetChanged(). Se garantiza que esta llamada se completará antes de que los metadatos o los datos de vista se obtengan del RemoteViewsFactory. Además, puedes realizar operaciones de procesamiento intensivo dentro del método getViewAt(). Si esta llamada se ejecuta con lentitud, se mostrará la vista de carga (especificada por el método getLoadingView() de RemoteViewsFactory) en la posición correspondiente de la vista de colecciones hasta que se muestre un resultado.