¿Cómo crear un widget de aplicación con una actividad de configuración y actualizarla por primera vez?

Esto me está volviendo loca. No sé cómo actualizar el widget de la aplicación de la actividad de configuración, incluso con las prácticas recomendadas. ¿Por qué el método de actualización no se llama en la creación de widget de la aplicación es más allá de mi entendimiento.

Lo que me gustaría: un widget de aplicación que contiene una colección (con un listview) de elementos. Pero el usuario necesita seleccionar algo, así que necesito una actividad de configuración.

La actividad de configuración es ListActivity :

 @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ChecksWidgetConfigureActivity extends SherlockListActivity { private List<Long> mRowIDs; int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; private BaseAdapter mAdapter; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setResult(RESULT_CANCELED); setContentView(R.layout.checks_widget_configure); final Intent intent = getIntent(); final Bundle extras = intent.getExtras(); if (extras != null) { mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } // If they gave us an intent without the widget id, just bail. if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { finish(); } mRowIDs = new ArrayList<Long>(); // it's actually loaded from an ASyncTask, don't worry about that — it works. mAdapter = new MyListAdapter((LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)); getListView().setAdapter(mAdapter); } private class MyListAdapter extends BaseAdapter { // not relevant... } @Override protected void onListItemClick(final ListView l, final View v, final int position, final long id) { if (position < mRowIDs.size()) { // Set widget result final Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); resultValue.putExtra("rowId", mRowIDs.get(position)); setResult(RESULT_OK, resultValue); // Request widget update final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); ChecksWidgetProvider.updateAppWidget(this, appWidgetManager, mAppWidgetId, mRowIDs); } finish(); } } 

Como puede ver, estoy llamando a un método estático de mi proveedor de widget de aplicación. Tengo esa idea del documento oficial .

Echemos un vistazo a mi proveedor:

 @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public class ChecksWidgetProvider 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"; @Override public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, 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++) { // Here we setup the intent which points to the StackViewService which will // provide the views for this collection. final Intent intent = new Intent(context, ChecksWidgetService.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))); final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.checks_widget); rv.setRemoteAdapter(android.R.id.list, intent); // The empty view is displayed when the collection has no items. It should be a sibling // of the collection view. rv.setEmptyView(android.R.id.list, android.R.id.empty); // Here we setup the a pending intent template. Individuals items of a collection // cannot setup their own pending intents, instead, the collection as a whole can // setup a pending intent template, and the individual items can set a fillInIntent // to create unique before on an item to item basis. final Intent toastIntent = new Intent(context, ChecksWidgetProvider.class); toastIntent.setAction(ChecksWidgetProvider.TOAST_ACTION); toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]); toastIntent.setData(Uri.parse(toastIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setPendingIntentTemplate(android.R.id.list, toastPendingIntent); appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } } @Override public void onReceive(final Context context, final Intent intent) { final AppWidgetManager mgr = AppWidgetManager.getInstance(context); if (intent.getAction().equals(TOAST_ACTION)) { final int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); final long rowId = intent.getLongExtra("rowId", 0); final int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0); Toast.makeText(context, "Touched view " + viewIndex + " (rowId: " + rowId + ")", Toast.LENGTH_SHORT).show(); } super.onReceive(context, intent); } @Override public void onAppWidgetOptionsChanged(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId, final Bundle newOptions) { updateAppWidget(context, appWidgetManager, appWidgetId, newOptions.getLong("rowId")); } public static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId, final long rowId) { final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.checks_widget); appWidgetManager.updateAppWidget(appWidgetId, views); } } 

Esto es básicamente una copia / pega del documento oficial. Podemos ver mi método estático aquí. Supongamos que en realidad utiliza el rowId por ahora.

También podemos ver otro intento fallido (ver más abajo) de actualizar el widget de la aplicación cuando recibo las opciones que se han cambiado en broadcast ( onAppWidgetOptionsChanged ).

El Service requerido para un widget de aplicación basado en colecciones es casi una copia / pegada exacta del documento:

 @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ChecksWidgetService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(final Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } } class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final int mCount = 10; private final List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>(); private final Context mContext; private final int mAppWidgetId; private final long mRowId; public StackRemoteViewsFactory(final Context context, final Intent intent) { mContext = context; mAppWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); mRowId = intent.getLongExtra("rowId", 0); } @Override 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 < mCount; i++) { mWidgetItems.add(new WidgetItem(i + " (rowId: " + mRowId + ") !")); } // We sleep for 3 seconds here to show how the empty view appears in the interim. // The empty view is set in the StackWidgetProvider and should be a sibling of the // collection view. try { Thread.sleep(3000); } catch (final InterruptedException e) { e.printStackTrace(); } } @Override public void onDestroy() { // In onDestroy() you should tear down anything that was setup for your data source, // eg. cursors, connections, etc. mWidgetItems.clear(); } @Override public int getCount() { return mCount; } @Override public RemoteViews getViewAt(final int position) { // position will always range from 0 to getCount() - 1. // We construct a remote views item based on our widget item xml file, and set the // text based on the position. final RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item); rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text); // Next, we set a fill-intent which will be used to fill-in the pending intent template // which is set on the collection view in StackWidgetProvider. final Bundle extras = new Bundle(); extras.putInt(ChecksWidgetProvider.EXTRA_ITEM, position); final Intent fillInIntent = new Intent(); fillInIntent.putExtras(extras); rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent); // You can do heaving lifting in here, synchronously. For example, if you need to // process an image, fetch something from the network, etc., it is ok to do it here, // synchronously. A loading view will show up in lieu of the actual contents in the // interim. try { Ld("Loading view " + position); Thread.sleep(500); } catch (final InterruptedException e) { e.printStackTrace(); } // Return the remote views object. return rv; } @Override public RemoteViews getLoadingView() { // You can create a custom loading view (for instance when getViewAt() is slow.) If you // return null here, you will get the default loading view. return null; } @Override public int getViewTypeCount() { return 1; } @Override public long getItemId(final int position) { return position; } @Override public boolean hasStableIds() { return true; } @Override public void onDataSetChanged() { // This is triggered when you call AppWidgetManager notifyAppWidgetViewDataChanged // on the collection view corresponding to this factory. You can do heaving lifting in // here, synchronously. For example, if you need to process an image, fetch something // from the network, etc., it is ok to do it here, synchronously. The widget will remain // in its current state while work is being done here, so you don't need to worry about // locking up the widget. } } 

Y por último, mi diseño de widget:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/widgetLayout" android:orientation="vertical" android:padding="@dimen/widget_margin" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/resizeable_widget_title" style="@style/show_subTitle" android:padding="2dp" android:paddingLeft="5dp" android:textColor="#FFFFFFFF" android:background="@drawable/background_pink_striked_transparent" android:text="@string/show_title_key_dates" /> <ListView android:id="@android:id/list" android:layout_marginRight="5dp" android:layout_marginLeft="5dp" android:background="@color/timeline_month_dark" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:id="@android:id/empty" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textColor="#ffffff" android:textStyle="bold" android:text="@string/empty_view_text" android:textSize="20sp" /> </LinearLayout> 

La sección relevante de mi archivo XML de manifiesto de Android:

 <receiver android:name="com.my.full.pkg.ChecksWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/checks_widget_info" /> </receiver> <activity android:name="com.my.full.pkg.ChecksWidgetConfigureActivity"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> </intent-filter> </activity> <service android:name="com.my.full.pkg.ChecksWidgetService" android:permission="android.permission.BIND_REMOTEVIEWS" /> 

xml/checks_widget_info.xml :

 <?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="146dp" android:minHeight="146dp" android:updatePeriodMillis="86400000" android:initialLayout="@layout/checks_widget" android:configure="com.my.full.pkg.ChecksWidgetConfigureActivity" android:resizeMode="horizontal|vertical" android:previewImage="@drawable/resizeable_widget_preview" /> 

¿Así que qué hay de malo? Bueno, cuando creo el widget está vacío. Quiero decir vacío. Vacío. Nada. ¡No tengo la vista vacía definida en mi diseño! ¿Que demonios?

Si vuelvo a instalar la aplicación o reiniciar el dispositivo (o eliminar la aplicación de lanzamiento), el widget de la aplicación se actualiza y contiene los 10 elementos que se agregan automáticamente como en el ejemplo.

No puedo conseguir la maldita cosa para actualizar después de que termine la actividad de configuración. Esta oración, tomada del documento, está más allá de mí: " El método onUpdate () no será llamado cuando se crea el Widget de la aplicación – […] sólo se omite la primera vez ".

Mi pregunta es:

  • ¿Por qué en el mundo el equipo de desarrollo de Android decidió no llamar a la actualización por primera vez que se creó el widget?
  • ¿Cómo puedo actualizar mi widget de aplicación antes de que finalice la actividad de configuración?

Otra cosa que no entiendo es el flujo de acción:

  1. Instala la aplicación con el último código compilado, prepara el espacio en el lanzador, abre el menú "widgets" desde el lanzador
  2. Elija mi widget y colóquelo en el área deseada
  3. En ese momento, mi proveedor de widget de aplicación recibe android.appwidget.action.APPWIDGET_ENABLED y luego android.appwidget.action.APPWIDGET_UPDATE
  4. Entonces mi proveedor de widget de aplicación recibe su método onUpdate . Esperaba que esto sucediera DESPUÉS de que la actividad de configuración terminara …
  5. Mi actividad de configuración se inicia. Pero el widget de la aplicación parece ya creado y actualizado, que no entiendo.
  6. Elijo el elemento de mi actividad de configuración: onListItemClick se llama
  7. Se updateAppWidget al updateAppWidget estático de mi proveedor, intentando desesperadamente actualizar el widget.
  8. La actividad de configuración establece su resultado y termina.
  9. El proveedor recibe android.appwidget.action.APPWIDGET_UPDATE_OPTIONS : bueno, eso tiene mucho sentido para recibir una actualización de tamaño cuando se crea. Ahí es donde llamo desesperadamente updateAppWidget
  10. onUpdate de mi proveedor no es llamado. ¡¡¿¿Por qué??!!

Al final: el widget está vacío. No listview-vacío o @android: id / empty-empty, really EMPTY . No se muestra ninguna vista. Nada.
Si vuelvo a instalar la aplicación, el widget de la aplicación se rellena con vistas dentro de la vista de lista, como se esperaba.
Cambiar el tamaño del widget no tiene efecto. Sólo llama onAppWidgetOptionsChanged nuevo, que no tiene efecto.

Lo que quiero decir con vacío: el diseño del widget de la aplicación está inflado, pero la vista de lista NO está inflada y la vista vacía NO se muestra.

El inconveniente de hacer la actualización a través de AppWidgetManager es que usted tiene que proporcionar las RemoteViews que – desde un punto de vista de diseño – no tiene sentido ya que la lógica relacionada con RemoteViews debe encapsularse dentro de AppWidgetProvider (o en su caso en el RemoteViewsService.RemoteViewsFactory).

El enfoque de SciencyGuy para exponer la lógica RemoteViews a través de un método estático es una forma de lidiar con eso, pero hay una solución más elegante que envía una transmisión directamente al widget:

 Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, ChecksWidgetProvider.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {mAppWidgetId}); sendBroadcast(intent); 

Como consecuencia, el método onUpdate () de AppWidgetProvider se llamará para crear las RemoteViews para el widget.

Está correcto que el método onUpdate no se active después de que termine la actividad de configuración. Depende de su actividad de configuración realizar la actualización inicial. Así que necesitas construir la vista inicial.

Esta es la esencia de lo que uno debe hacer al final de la configuración:

 // First set result OK with appropriate widgetId Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); setResult(RESULT_OK, resultValue); // Build/Update widget AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getApplicationContext()); // This is equivalent to your ChecksWidgetProvider.updateAppWidget() appWidgetManager.updateAppWidget(appWidgetId, ChecksWidgetProvider.buildRemoteViews(getApplicationContext(), appWidgetId)); // Updates the collection view, not necessary the first time appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.notes_list); // Destroy activity finish(); 

Ya estableció el resultado correctamente. Y llama a ChecksWidgetProvider.updateAppWidget (), sin embargo updateAppWidget () no devuelve el resultado correcto.

UpdateAppWidget () en current devuelve un objeto RemoteViews vacío. Lo que explica por qué tu widget está completamente vacío al principio. No has llenado la vista con nada. Le sugiero que mueva su código de onUpdate a un método static buildRemoteViews () que puede llamar desde onUpdate y updateAppWidget ():

 public static RemoteViews buildRemoteViews(final Context context, final int appWidgetId) { final RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.checks_widget); rv.setRemoteAdapter(android.R.id.list, intent); // The empty view is displayed when the collection has no items. It should be a sibling // of the collection view. rv.setEmptyView(android.R.id.list, android.R.id.empty); // Here we setup the a pending intent template. Individuals items of a collection // cannot setup their own pending intents, instead, the collection as a whole can // setup a pending intent template, and the individual items can set a fillInIntent // to create unique before on an item to item basis. final Intent toastIntent = new Intent(context, ChecksWidgetProvider.class); toastIntent.setAction(ChecksWidgetProvider.TOAST_ACTION); toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); toastIntent.setData(Uri.parse(toastIntent.toUri(Intent.URI_INTENT_SCHEME))); final PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); rv.setPendingIntentTemplate(android.R.id.list, toastPendingIntent); return rv; } public static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId) { final RemoteViews views = buildRemoteViews(context, appWidgetId); appWidgetManager.updateAppWidget(appWidgetId, views); } @Override public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); // Perform this loop procedure for each App Widget that belongs to this provider for (int appWidgetId: appWidgetIds) { RemoteViews rv = buildRemoteViews(context, appWidgetId); appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } } 

Eso debería ocuparse de la inicialización del widget.

El último paso antes de llamar a finish () en mi código de ejemplo es actualizar la vista de colección. Como dice el comentario, esto no es necesario la primera vez. Sin embargo, lo incluyo solo en caso de que tengas la intención de permitir que un widget se vuelva a configurar después de haber sido agregado. En ese caso, se debe actualizar manualmente la vista de recopilación para asegurarse de que se cargan las vistas y datos apropiados.

No vi su appwidgetprovider.xml y AndroidManifest.xml, pero supongo que no configuró correctamente su actividad de configuración.

A continuación, le indicamos cómo hacerlo:

  1. Agregue el siguiente atributo a su appwidgetprovider.xml:

     <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:configure="com.full.package.name.ChecksWidgetConfigureActivity" ... /> 
  2. Su actividad de configuración debe tener un intent-filter adecuado:

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

Si la actividad de configuración está configurada correctamente, onUpdate() sólo se activa cuando finaliza.

  • Mapas (colección) que mantiene la orden de inserción en java
  • ¿Cómo puedo clasificar mi Arraylist alfabético? (Java)
  • Gradle, define una colección de propiedades
  • Colecciones en ORMLite
  • Cómo inicializar un SparseArray estático
  • Cómo reemplazar el valor en clave particular en LinkedHashMap
  • (Un) boxeo primitivo arrays en Java
  • Problemas con ORMLite y colecciones perezosas
  • Android: ArrayList Mover el elemento a la posición 0
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.