Participe do evento ⁠#Android11: apresentação de lançamento da versão Beta no dia 3 de junho.

Criar um widget de app

Widgets de app são visualizações de aplicativos em miniatura que podem ser incorporadas em outros aplicativos (como a tela inicial) e receber atualizações periódicas. Essas visualizações são chamadas de "widgets" na interface do usuário, e é possível publicá-las com um provedor de widgets de app. O componente de um aplicativo que pode reter outros widgets de apps é chamado de host de widgets de app. A captura de tela abaixo mostra o widget do app de música.

Este documento descreve como publicar um widget de app usando um provedor. Para ver uma discussão sobre como criar seu próprio AppWidgetHost para hospedar widgets de apps, consulte Host de widgets de app.

Observação: para saber mais informações sobre como projetar seu widget de app, leia a Visão geral de widgets de apps.

Noções básicas

Para criar um widget de app, você precisa do seguinte:

Objeto AppWidgetProviderInfo
Descreve os metadados de um widget de app, como o layout, a frequência de atualização e a classe AppWidgetProvider do widget. Isso precisa ser definido em XML.
Implementação da classe AppWidgetProvider
Define os métodos básicos que permitem interagir programaticamente com o widget de app, com base em eventos de transmissão. Por meio dela, você receberá transmissões quando o widget de app for atualizado, ativado, desativado e excluído.
Layout de visualização
Define o layout inicial do widget de app, definido em XML.

Além disso, é possível implementar uma atividade para configurar o widget de app. Ela é uma Activity opcional que é iniciada quando o usuário adiciona seu widget de app e permite que ele modifique as configurações do widget no momento da criação.

As seções a seguir descrevem como configurar cada um desses componentes.

Declarar um widget de app no manifesto

Primeiro, declare a classe AppWidgetProvider no arquivo AndroidManifest.xml do seu aplicativo. Exemplo:

    <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>
    

O elemento <receiver> requer o atributo android:name, que especifica o AppWidgetProvider usado pelo widget de app.

É necessário que o elemento <intent-filter> inclua um elemento <action> com o atributo android:name. Esse atributo especifica que o AppWidgetProvider aceita a transmissão de ACTION_APPWIDGET_UPDATE. Essa é a única transmissão que precisa ser declarada explicitamente. O AppWidgetManager envia automaticamente todas as outras transmissões do widget de app para o AppWidgetProvider conforme necessário.

O elemento <meta-data> especifica o recurso AppWidgetProviderInfo e requer os seguintes atributos:

  • android:name: especifica o nome dos metadados. Use android.appwidget.provider para identificar os dados como o descritor AppWidgetProviderInfo.
  • android:resource: especifica o local do recurso AppWidgetProviderInfo.

Adicionar os metadados AppWidgetProviderInfo

AppWidgetProviderInfo define as qualidades essenciais de um widget de app, como as dimensões mínimas do layout, o recurso de layout inicial, a frequência de atualização do widget de app e, opcionalmente, uma atividade de configuração para iniciar no momento da criação. Defina o objeto AppWidgetProviderInfo em um recurso XML usando um único elemento <appwidget-provider> e salve-o na pasta res/xml/ do projeto.

Exemplo:

    <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>
    

Veja um resumo dos atributos <appwidget-provider>:

  • Os valores para os atributos minWidth e minHeight especificam a quantidade mínima de espaço que o widget do app consome por padrão. A tela inicial padrão posiciona widgets de app na janela com base em uma grade de células com altura e largura definidas. Se os valores da largura ou da altura mínima de um widget de app não corresponderem às dimensões das células, as dimensões do widget serão arredondadas para cima para o tamanho de célula mais próximo.

    Consulte as Diretrizes de design para widgets de app para ver mais informações sobre o dimensionamento dos seus widgets de apps.

    Observação: para que seu widget de app possa ser usado em vários dispositivos, o tamanho mínimo dele não pode ser maior que células 4 x 4.

  • Os atributos minResizeWidth e minResizeHeight especificam o tamanho mínimo absoluto do widget do app. Esses valores precisam especificar o tamanho abaixo do qual o widget de app ficaria ilegível ou inutilizável. Usar esses atributos permite que o usuário redimensione o widget para um tamanho que pode ser menor que o tamanho de widget padrão definido pelos atributos minWidth e minHeight. Introduzido no Android 3.1.

    Consulte as Diretrizes de design para widgets de app para ver mais informações sobre o dimensionamento dos seus widgets de apps.

  • O atributo updatePeriodMillis define a frequência em que o framework do widget de app solicita uma atualização do AppWidgetProvider chamando o método de callback onUpdate(). Não há garantias de que a atualização real ocorra no momento exato com esse valor. Sugerimos atualizar do modo mais infrequente possível, talvez não mais do que uma vez por hora, para conservar a bateria. Você também pode permitir que o usuário ajuste a frequência em uma configuração. Algumas pessoas podem querer um mostrador de ações atualizado a cada 15 minutos, ou talvez apenas quatro vezes ao dia.

    Observação: se o dispositivo estiver inativo quando chegar a hora de uma atualização (conforme definido por updatePeriodMillis), ele será ativado para que ela seja realizada. Se você não atualizar mais de uma vez por hora, isso provavelmente não causará problemas significativos para a duração da bateria. No entanto, se for necessário atualizar com uma maior frequência e/ou não for necessário atualizar enquanto o dispositivo estiver inativo, você poderá realizar as atualizações com base em um alarme que não ativará o dispositivo. Para fazer isso, defina um alarme com um intent que seu AppWidgetProvider recebe, usando o AlarmManager. Configure o tipo de alarme como ELAPSED_REALTIME ou RTC, que só executa o alarme quando o dispositivo está ativado. Então, configure updatePeriodMillis como zero ("0").

  • O atributo initialLayout aponta para o recurso de layout que define o layout do widget de app.
  • O atributo configure define a Activity a ser iniciada quando os usuários adicionarem o widget de app, para que eles configurem as propriedades do widget. Isso é opcional. Leia Criar uma atividade para configurar widgets de app abaixo.
  • O atributo previewImage especifica uma visualização da aparência do widget de app após a configuração, que o usuário verá ao selecionar o widget. Se ele não for fornecido, o usuário verá o ícone do seu aplicativo na tela de início. Esse campo corresponde ao atributo android:previewImage no elemento <receiver> no arquivo AndroidManifest.xml. Para ver mais discussões sobre como usar previewImage, consulte Configurar uma imagem de visualização. Introduzido no Android 3.0.
  • O atributo autoAdvanceViewId especifica o ID de visualização da subexibição do widget de app, que precisa ser avançado automaticamente pelo host do widget. Introduzido no Android 3.0.
  • O atributo resizeMode especifica as regras pelas quais um widget pode ser redimensionado. Esse atributo é usado para permitir o redimensionamento dos widgets da tela inicial: na horizontal, vertical ou nos dois eixos. Os usuários tocam em um widget e o mantêm pressionado para exibir alças de redimensionamento e, em seguida, arrastam as alças horizontais e/ou verticais para mudar o tamanho na grade do layout. Os valores para o atributo resizeMode incluem "horizontal", "vertical" e "nenhum". Para declarar um widget como redimensionável na horizontal e na vertical, forneça o valor "horizontal|vertical". Introduzido no Android 3.1.
  • O atributo minResizeHeight especifica a altura mínima (em dps) para a qual o widget pode ser redimensionado. Esse campo não terá efeito se for maior do que minHeight ou se o redimensionamento vertical não estiver ativado (consulte resizeMode). Introduzido no Android 4.0.
  • O atributo minResizeWidth especifica a largura mínima (em dps) para a qual o widget pode ser redimensionado. Esse campo não terá efeito se for maior do que minWidth ou se o redimensionamento horizontal não estiver ativado (consulte resizeMode). Introduzido no Android 4.0.
  • O atributo widgetCategory declara se o widget de app pode ser exibido na tela inicial (home_screen), na tela de bloqueio (keyguard) ou nas duas. Apenas versões do Android anteriores à 5.0 são compatíveis com widgets da tela de bloqueio. Para o Android 5.0 e versões posteriores, apenas home_screen é válido.

Consulte a classe AppWidgetProviderInfo para saber mais informações sobre os atributos aceitos pelo elemento <appwidget-provider>.

Criar o layout do widget de app

É necessário definir um layout inicial para seu widget de app em XML e salvá-lo no diretório res/layout/ do projeto. Você pode projetar seu widget de app usando os objetos de visualização listados abaixo, mas, antes de começar, leia e entenda as Diretrizes de design para widgets de app.

Criar o layout do widget de app é simples se você conhecer os Layouts. De qualquer forma, você precisa estar ciente de que os layouts para widgets de apps são baseados em RemoteViews, que não são compatíveis com todos os tipos de widget de visualização ou layout.

Um objeto RemoteViews (e, consequentemente, um widget de app) pode ser compatível com as seguintes classes de layout:

E as seguintes classes de widget:

Os descendentes dessas classes não são compatíveis.

Os RemoteViews também são compatíveis com ViewStub, que é uma visualização invisível e de tamanho zero que você pode usar para inflar lentamente os recursos de layout no momento da execução.

Adicionar margens aos widgets de apps

Em geral, os widgets não se estendem até as bordas da tela e não estão visualmente alinhados com outros widgets. Por isso, adicione margens em todos os lados ao redor do frame do seu widget.

A partir do Android 4.0, os widgets de apps recebem padding automaticamente entre o frame e a caixa delimitadora do widget de app para fornecer um melhor alinhamento com outros widgets e ícones na tela inicial do usuário. Para aproveitar esse comportamento altamente recomendável, configure a targetSdkVersion do seu aplicativo para 14 ou mais.

É fácil criar um único layout que tenha margens personalizadas aplicadas a versões anteriores da plataforma e não tenha margens extras para o Android 4.0 e versões posteriores:

  1. Defina a targetSdkVersion do seu aplicativo como 14 ou mais.
  2. Crie um layout como o mostrado abaixo, que faça referência a um recurso de dimensão para as margens:
        <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. Crie recursos de duas dimensões, uma em res/values/ para fornecer as margens personalizadas pré-Android 4.0, e outra em res/values-v14/ para não fornecer padding extra para widgets do Android 4.0:

    res/values/dimens.xml:

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

    res/values-v14/dimens.xml:

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

Outra opção é simplesmente criar margens extras nos seus recursos nine-patch de segundo plano por padrão e fornecer nine-patches diferentes sem margens para a API de nível 14 ou posterior.

Usar a classe AppWidgetProvider

A classe AppWidgetProvider estende o BroadcastReceiver como uma classe de conveniência para lidar com as transmissões do widget de app. O AppWidgetProvider recebe apenas as transmissões de eventos que são relevantes para o widget de app, como quando ele é atualizado, excluído, ativado e desativado. Quando esses eventos de transmissão ocorrem, o AppWidgetProvider recebe as seguintes chamadas de método:

onUpdate()
É chamado para atualizar o widget de app em intervalos definidos pelo atributo updatePeriodMillis em AppWidgetProviderInfo. Consulte Adicionar os metadados AppWidgetProviderInfo acima. Esse método também é chamado quando o usuário adiciona o widget de app. Por isso, ele precisa realizar uma configuração essencial, como definir manipuladores de eventos para visualizações e iniciar um Service temporário, se necessário. No entanto, se você tiver declarado uma atividade de configuração, esse método não será chamado quando o usuário adicionar o widget de app, mas será chamado para as atualizações subsequentes. É responsabilidade da atividade de configuração realizar a primeira atualização quando a configuração for concluída. Consulte Criar uma atividade para configuração de widgets de apps abaixo.
onAppWidgetOptionsChanged()
É chamado quando o widget é posicionado pela primeira vez e sempre que ele é redimensionado. É possível usar esse callback para mostrar ou ocultar conteúdo com base nas variações de tamanho do widget. As variações de tamanho são recebidas ao chamar getAppWidgetOptions(), que retorna um Bundle que inclui o seguinte:

Esse callback foi introduzido na API de nível 16 (Android 4.1). Se você implementar esse callback, seu aplicativo não pode depender dele, porque ele não será chamado em dispositivos mais antigos.
onDeleted(Context, int[])
É chamado toda vez que um widget de app é excluído do host de widgets de apps.
onEnabled(Context)
É chamado quando uma instância do widget de app é criada pela primeira vez. Por exemplo, se o usuário adicionar duas instâncias do seu widget de app, ele só será chamado na primeira vez. Se você precisar abrir um novo banco de dados ou realizar outra configuração que só precise ocorrer uma vez para todas as instâncias de widget de app, esse é um bom lugar para fazer isso.
onDisabled(Context)
É chamado quando a última instância do seu widget de app é excluída do host de widgets. Aqui é onde você precisa limpar todo o trabalho feito em onEnabled(Context), por exemplo, excluir um banco de dados temporário.
onReceive(Context, Intent)
É chamado para cada transmissão e antes de cada um dos métodos de callback mencionados acima. Normalmente, não é necessário implementar esse método, porque a implementação padrão do AppWidgetProvider filtra todas as transmissões do widget de app e chama os métodos acima conforme apropriado.

É necessário declarar sua implementação da classe AppWidgetProvider como um broadcast receiver usando o elemento <receiver> no AndroidManifest. Consulte Declarar um widget de app no manifesto acima.

O callback mais importante do AppWidgetProvider é onUpdate(), porque ele é chamado quando cada widget de app é adicionado a um host, a menos que você use uma atividade de configuração. Se o widget de app aceitar eventos de interação do usuário, será necessário registrar os manipuladores de eventos nesse callback. Se o widget de app não criar arquivos ou bancos de dados temporários nem realizar outro trabalho que exija a limpeza, onUpdate() talvez seja o único método de callback que você precisa definir. Por exemplo, se você quiser um widget de app com um botão que, quando clicado, inicie uma atividade, você pode usar a seguinte implementação do 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);
            }
        }
    }
    

Esse AppWidgetProvider define apenas o método onUpdate() para o propósito de definir um PendingIntent que inicie uma Activity e anexe-o ao botão do widget de app com setOnClickPendingIntent(int, PendingIntent). Observe que ele inclui um loop que é iterado em cada entrada em appWidgetIds, que é uma matriz de IDs que identificam cada widget de app criado por esse provedor. Dessa forma, se o usuário criar mais de uma instância do widget de app, todas elas serão atualizadas simultaneamente. No entanto, apenas uma programação de updatePeriodMillis será gerenciada para todas as instâncias do widget de app. Por exemplo, se a programação de atualizações for definida a cada duas horas, e uma segunda instância do widget de app for adicionada uma hora após a primeira, as duas serão atualizadas no período definido pela primeira. O período da segunda atualização será ignorado, e as duas serão atualizadas a cada duas horas, não a cada hora.

Observação: como AppWidgetProvider é uma extensão do BroadcastReceiver, não há garantias de que seu processo continuará em execução depois que os métodos de callback retornarem. Consulte BroadcastReceiver para informações sobre o ciclo de vida da transmissão. Se o processo de configuração do seu widget de app levar vários segundos, talvez enquanto realiza solicitações na Internet, e você precisar que o processo continue, recomendamos iniciar um Service no método onUpdate(). De dentro do serviço, é possível executar suas próprias atualizações para o widget de app sem se preocupar com o AppWidgetProvider fechando por causa de um erro O app não está respondendo (ANR, na sigla em inglês). Consulte o AppWidgetProvider de amostra do Wiktionary para ver um exemplo de um widget de app executando um Service.

Veja também a classe de amostra ExampleAppWidgetProvider.java.

Receber intents de transmissão do widget de app

AppWidgetProvider é apenas uma classe de conveniência. Se você quiser receber as transmissões de widgets de apps diretamente, implemente seu próprio BroadcastReceiver ou modifique o callback do onReceive(Context, Intent). Os intents com os quais você precisa se preocupar são os seguintes:

Fixar widgets de app

Em dispositivos com Android 8.0 ou versões posteriores, as telas de início que permitem criar atalhos fixados também permitem fixar widgets de app. Assim como os atalhos, esses widgets fixados oferecem aos usuários acesso a tarefas específicas no seu aplicativo.

No seu app, é possível criar uma solicitação para o sistema fixar um widget em uma tela de início compatível realizando a seguinte sequência de etapas:

  1. Crie o widget no arquivo de manifesto do seu app, conforme mostrado no snippet a seguir:
        <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. Chame o método requestPinAppWidget(), conforme mostrado no snippet de código a seguir:

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

Observação: caso seu app não precise ser notificado se o sistema fixou ou não um widget em uma tela de início compatível, você pode transmitir null como o terceiro argumento para requestPinAppWidget().

Criar uma atividade para configurar widgets de app

Se quiser que o usuário defina as configurações quando adicionar um novo widget de app, você pode criar uma atividade para configurar o widget. Essa Activity será iniciada automaticamente pelo host do widget de app e permitirá que o usuário defina as configurações disponíveis para o widget no momento da criação, como a cor, o tamanho, o período de atualização ou outras configurações de funcionalidade.

A atividade de configuração será declarada como uma atividade normal no arquivo de manifesto do Android. No entanto, ela será iniciada pelo host do widget de app com a ação ACTION_APPWIDGET_CONFIGURE. Por isso, é necessário que a atividade aceite esse intent. Exemplo:

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

Além disso, é necessário que a atividade seja declarada no arquivo XML AppWidgetProviderInfo, com o atributo android:configure. Consulte Adicionar os metadados AppWidgetProviderInfo acima. Por exemplo, a atividade de configuração pode ser declarada desta forma:

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

Observe que a atividade é declarada com um namespace totalmente qualificado, porque ela será referenciada fora do escopo do seu pacote.

Isso é tudo o que você precisa para começar a usar uma atividade de configuração. Agora, você só precisa da atividade em si. No entanto, há duas observações importantes ao implementar a atividade:

  • O host de widget de app chama a atividade de configuração, e é necessário que ela sempre retorne um resultado. O resultado precisa incluir o ID do widget de app transmitido pelo intent que iniciou a atividade, salva nos extras do intent como EXTRA_APPWIDGET_ID.
  • O método onUpdate() não será chamado quando o widget de app for criado. O sistema não enviará a transmissão ACTION_APPWIDGET_UPDATE quando uma atividade de configuração for iniciada. É responsabilidade da atividade de configuração solicitar uma atualização do AppWidgetManager quando o widget de app for criado pela primeira vez. No entanto, onUpdate() será chamado para atualizações subsequentes. Ele é ignorado apenas na primeira vez.

Consulte os snippets de código na seção a seguir para ver um exemplo de como retornar um resultado da configuração e atualizar o widget de app.

Atualizar o widget de app pela atividade de configuração

Quando um widget de app usa uma atividade de configuração, é responsabilidade da atividade atualizar o widget quando a configuração for concluída. É possível fazer isso solicitando uma atualização diretamente do AppWidgetManager.

Veja um resumo do procedimento para atualizar adequadamente o widget de app e fechar a atividade de configuração:

  1. Primeiro, adquira o ID do widget pelo intent que iniciou a atividade:

    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. Faça a configuração do widget de app.
  3. Quando a configuração for concluída, adquira uma instância do AppWidgetManager chamando getInstance(Context):

    Kotlin

        val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(context)
        

    Java

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        
  4. Atualize o widget de app com um layout RemoteViews chamando 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 fim, crie o intent de retorno, defina-o com o resultado da atividade e conclua a atividade:

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

Dica: quando a atividade de configuração for aberta pela primeira vez, defina o resultado dela como RESULT_CANCELED, junto com EXTRA_APPWIDGET_ID, conforme mostrado na etapa 5 acima. Dessa forma, se o usuário desistir da atividade antes de chegar ao fim, o host do widget de app será notificado de que a configuração foi cancelada, e o widget de app não será adicionado.

Consulte a classe de amostra ExampleAppWidgetConfigure.java no ApiDemos para ver um exemplo.

Definir uma imagem de visualização

O Android 3.0 introduz o campo previewImage, que especifica uma visualização da aparência do widget de app. Essa visualização é exibida para o usuário pelo seletor de widgets. Se esse campo não for fornecido, o ícone do widget do app será usado para a visualização.

É assim que se especifica essa configuração em XML:

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

Com o objetivo de ajudar a criar uma imagem de visualização para seu widget de app, para especificar no campo previewImage, o Android Emulator inclui um aplicativo chamado "Widget Preview". Para criar uma imagem de visualização, abra esse aplicativo, selecione o widget do seu app e configure como você quer exibir a imagem. Em seguida, salve-a e coloque-a nos recursos drawable do seu aplicativo.

Usar widgets de apps com coleções

O Android 3.0 introduz widgets de apps com coleções. Esses tipos de widgets de apps usam o RemoteViewsService para exibir coleções que usam dados remotos, como de um provedor de conteúdo. Os dados fornecidos pelo RemoteViewsService são apresentados no widget de app usando um dos tipos de visualização a seguir, que chamaremos de "visualizações de coleção":

ListView
Uma visualização que mostra itens em uma lista de rolagem vertical. Por exemplo, veja o widget do app do Gmail.
GridView
Uma visualização que mostra itens na grade de rolagem bidimensional. Por exemplo, veja o widget do app Favoritos.
StackView
Uma visualização de cards empilhados (semelhante a um calendário de mesa), na qual o usuário pode mover o card da frente para cima ou para baixo para ver o card anterior ou seguinte, respectivamente. Alguns exemplos são os widgets dos apps YouTube e Livros.
AdapterViewFlipper
Um ViewAnimator simples apoiado por um adaptador e que execução a animação entre duas ou mais visualizações. Apenas um filho é exibido por vez.

Conforme mencionado acima, essas visualizações de coleção exibem coleções apoiadas por dados remotos. Isso significa que elas usam um Adapter para vincular a interface do usuário aos dados. Um Adapter vincula itens individuais de um conjunto de dados em objetos View individuais. Como essas visualizações de coleção utilizam adaptadores, é necessário que o framework do Android inclua arquitetura extra para que haja compatibilidade com o uso em widgets de apps. No contexto de um widget de app, o Adapter é substituído por RemoteViewsFactory, que é simplesmente um wrapper fino ao redor da interface Adapter. Quando um item específico na coleção for solicitado, o RemoteViewsFactory criará e retornará o item para a coleção como um objeto RemoteViews. Para incluir uma visualização de coleção no seu widget de app, é necessário implementar RemoteViewsService e RemoteViewsFactory.

RemoteViewsService é um serviço que permite que um adaptador remoto solicite objetos RemoteViews. RemoteViewsFactory é uma interface para um adaptador entre uma visualização de coleção (como ListView, GridView e assim por diante) e os dados subjacentes dessa visualização. Veja um exemplo da amostra StackWidget de código clichê usado para implementar esse serviço e essa 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 {

    //... 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 exemplo

Os trechos de código nesta seção foram extraídos da amostra StackWidget:

Essa amostra consiste em uma pilha de 10 visualizações, que exibem os valores "0!" a "9!". O widget de app da amostra tem estes comportamentos principais:

  • O usuário pode deslocar verticalmente a visualização superior no widget do app para exibir a visualização seguinte ou anterior. Esse é um comportamento integrado do StackView.
  • Sem interações do usuário, o widget de app avança automaticamente pelas visualizações em sequência, como uma apresentação de slides. Isso acontece devido à configuração android:autoAdvanceViewId="@id/stack_view" no arquivo res/xml/stackwidgetinfo.xml. Essa configuração é aplicada ao ID de visualização, que, nesse caso, é o ID da visualização em pilha.
  • Se o usuário tocar na visualização superior, o widget de app exibirá a mensagem Toast "Visualização n tocada", em que n é o índice (posição) da visualização selecionada. Para saber mais sobre como isso é implementado, consulte Adicionar comportamentos a itens individuais.

Implementar widgets de app com coleções

Para implementar um widget de app com coleções, siga as mesmas etapas básicas que você usaria para implementar qualquer widget de app. As seções a seguir descrevem as etapas extras que você precisa seguir para implementar um widget de app com coleções.

Manifesto para widgets de apps com coleções

Além dos requisitos listados em Declarar um widget de app no manifesto, para permitir que os widgets de apps com coleções sejam vinculados ao seu RemoteViewsService, é necessário declarar o serviço no seu arquivo de manifesto com a permissão BIND_REMOTEVIEWS. Isso impede que outros aplicativos acessem livremente os dados do seu widget de app. Por exemplo, ao criar um widget de app que usa RemoteViewsService para preencher uma visualização de coleção, a entrada do manifesto pode ter a seguinte aparência:

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

A linha android:name="MyWidgetService" refere-se à sua subclasse de RemoteViewsService.

Layout para widgets de apps com coleções

O principal requisito para o XML file de layout do widget de app é que ele inclua uma das visualizações de coleção: ListView, GridView, StackView ou AdapterViewFlipper. Este é o widget_layout.xml para a amostra 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>

É necessário que as visualizações vazias sejam irmãs da visualização de coleção para que a visualização vazia represente um estado vazio.

Além do arquivo de layout do seu widget de app inteiro, é necessário criar outro arquivo de layout que defina o layout de cada item na coleção. Por exemplo, um layout para cada livro em uma coleção de livros. A amostra StackWidget tem apenas um arquivo de layout, widget_item.xml, uma vez que todos os itens usam o mesmo layout.

Classe AppWidgetProvider para widgets de apps com coleções

Como acontece com um widget de app normal, a maior parte do seu código na subclasse AppWidgetProvider geralmente vai para onUpdate(). A principal diferença na sua implementação para onUpdate() ao criar um widget de app com coleções é que você precisa chamar setRemoteAdapter(). Isso informa ao conjunto de visualização onde os dados precisam ser adquiridos. O RemoteViewsService pode retornar sua implementação de RemoteViewsFactory, e o widget pode exibir os dados apropriados. Ao chamar esse método, você precisa transmitir um intent que aponte para sua implementação de RemoteViewsService e o ID do widget de app que especifica o widget a ser atualizado.

Por exemplo, veja como a amostra StackWidget implementa o método de callback onUpdate() para definir o RemoteViewsService como o adaptador remoto para a coleção de widgets de apps:

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

Classe RemoteViewsService

Persistir dados

Conforme descrito acima, sua subclasse RemoteViewsService fornece o RemoteViewsFactory usado para preencher a visualização de coleção remota.

Especificamente, você precisa seguir estas etapas:

  1. Coloque RemoteViewsService em subclasse. RemoteViewsService é o serviço pelo qual um adaptador remoto pode solicitar RemoteViews.
  2. Na sua subclasse RemoteViewsService, inclua uma classe que implemente a interface RemoteViewsFactory. RemoteViewsFactory é uma interface para um adaptador entre uma visualização de coleção remota (como ListView, GridView e assim por diante) e os dados subjacentes dessa visualização. Sua implementação é responsável por criar um objeto RemoteViews para cada item no conjunto de dados. Essa interface é um wrapper fino em Adapter.

Você não pode depender de uma única instância do seu serviço, nem de qualquer dado contido nele, para persistir. Portanto, não é aconselhável armazenar dados no seu RemoteViewsService, a menos que sejam estáticos. Se você quiser que os dados do seu widget de app sejam persistidos, a melhor abordagem é usar um ContentProvider, cujos dados persistem além do ciclo de vida do processo.

O conteúdo principal da implementação do RemoteViewsService é o RemoteViewsFactory, descrito abaixo.

Interface RemoteViewsFactory

Sua classe personalizada que implementa a interface RemoteViewsFactory fornece ao widget de app os dados dos itens na coleção dela. Para fazer isso, ele combina o arquivo de layout XML do item de widget de app com uma fonte de dados. Essa fonte de dados pode ser qualquer coisa, desde um banco de dados até uma matriz simples. Na amostra StackWidget, a fonte de dados é uma matriz de WidgetItems. O RemoteViewsFactory funciona como um adaptador para colar os dados na visualização de coleção remota.

Os dois métodos mais importantes que você precisa implementar para sua subclasse RemoteViewsFactory são onCreate() e getViewAt().

O sistema chama onCreate() ao criar sua fábrica pela primeira vez. Esse é o lugar onde se configura todas as conexões e/ou cursores para sua fonte de dados. Por exemplo, a amostra StackWidget usa onCreate() para inicializar uma matriz de objetos WidgetItem. Quando o widget do app está ativo, o sistema acessa esses objetos usando a posição de índice deles na matriz, e o texto que eles contêm é exibido.

Aqui está um trecho da implementação de RemoteViewsFactory da amostra StackWidget que mostra partes do 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 + "!"));
            }
            ...
        }
    ...
    

O método RemoteViewsFactory para getViewAt() retorna um objeto RemoteViews correspondente aos dados no position especificado no conjunto de dados. Aqui está um trecho da implementação de RemoteViewsFactory da amostra 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;
    }
    

Adicionar comportamentos a itens individuais

As seções acima mostram como vincular seus dados à coleção de widgets de apps. Mas e se você quiser adicionar um comportamento dinâmico aos itens individuais na sua visualização de coleção?

Conforme descrito em Usar a classe AppWidgetProvider, normalmente se usa setOnClickPendingIntent() para definir o comportamento de clique de um objeto, como para fazer com que um botão inicie um Activity. No entanto, essa abordagem não é permitida para visualizações filhas em um item de coleção individual. Por exemplo, você pode usar setOnClickPendingIntent() para configurar um botão global no widget do app Gmail que inicia o app, mas não nos itens de lista individuais. Para adicionar o comportamento de clique a itens individuais em uma coleção, use setOnClickFillInIntent(). Isso envolve a configuração de um modelo de intent pendente para sua visualização de coleção e a definição de um intent de preenchimento em cada item na coleção por meio do seu RemoteViewsFactory.

Esta seção usa a amostra StackWidget para descrever como adicionar comportamentos a itens individuais. Na amostra StackWidget, se o usuário tocar na visualização superior, o widget do app exibirá a mensagem Toast "Visualização n tocada", em que n é o índice (posição) da visualização selecionada. Veja como funciona:

  • O StackWidgetProvider (uma subclasse AppWidgetProvider) cria um intent pendente que tem uma ação personalizada chamada TOAST_ACTION.
  • Quando o usuário toca em uma visualização, o intent é acionado e transmite TOAST_ACTION.
  • A transmissão é interceptada pelo método onReceive() do StackWidgetProvider, e o widget de app exibe a mensagem Toast para a visualização tocada. Os dados dos itens da coleção são fornecidos pelo RemoteViewsFactory, via RemoteViewsService.

Observação: a amostra StackWidget usa uma transmissão, mas um widget de app costuma simplesmente iniciar uma atividade em um cenário como esse.

Configurar o modelo de intent pendente

O StackWidgetProvider (subclasse AppWidgetProvider) configura um intent pendente. Os itens individuais de uma coleção não podem configurar os próprios intents pendentes. Em vez disso, a coleção como um todo configura um modelo de intent pendente, e os itens individuais definem um intent de preenchimento para criar um comportamento único para cada item.

Essa classe também recebe a transmissão que é enviada quando o usuário toca em uma visualização. Ela processa esse evento no método onReceive(). Se a ação do intent for TOAST_ACTION, o widget de app exibirá uma mensagem Toast para a visualização atual.

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);
        }
    }
    
Configuração do intent de preenchimento

Seu RemoteViewsFactory precisa definir um intent de preenchimento em cada item na coleção. Isso permite distinguir a ação de clique individual de um determinado item. O intent de preenchimento é então combinado com o modelo PendingIntent para determinar o intent final que será executado quando o item for clicado.

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

Manter os dados da coleção atualizados

A figura a seguir ilustra o fluxo que ocorre em um widget de app que usa coleções quando atualizações são realizadas. Ela mostra como o código do widget de app interage com o RemoteViewsFactory e como é possível acionar atualizações:

Um dos recursos de widgets de app que usam coleções é a possibilidade de fornecer aos usuários conteúdo atualizado. Por exemplo, considere o widget do app Gmail para Android 3.0, que fornece aos usuários um resumo da Caixa de entrada deles. Para que isso seja possível, você precisa acionar sua visualização de coleção e seu RemoteViewsFactory para buscar e exibir novos dados. Você faz isso com o AppWidgetManager chamando notifyAppWidgetViewDataChanged(). Essa chamada resulta em um callback para seu onDataSetChanged() de RemoteViewsFactory, que dá a oportunidade de buscar novos dados. Observe que é possível realizar operações de processamento intensivo de forma síncrona dentro do callback onDataSetChanged(). Você tem a garantia de que essa chamada será concluída antes que os dados de visualização ou os metadados sejam buscados em RemoteViewsFactory. Além disso, é possível realizar operações de processamento intenso no método getViewAt(). Se essa chamada levar tempo demais, a visualização de carregamento, especificada no método getLoadingView() de RemoteViewsFactory, será mostrada na posição correspondente da visualização do coleção até que ela retorne.