18

I have seen many questions along these lines, and I keep seeing the same answer over and over. I don't want to have to have a Service for every kind of widget my app has, especially seeing as my app already has 2 persistent services.

Specifically, if one of my existing services sees that data has changed, I want to update my widgets. Doing this on a timer is annoying, as it could be days between updates or there might be several within one hour. I want my widgets to ALWAYS show up to date information.

Android widget design seems to work on the basis that your widget pulls information when it wants it, I think there are many sensible scenarios where an activity may wish to push data to a widget.

Read the answer below for how I worked out how to do precisely this. As far as I can see there are no adverse effects if it is done properly.

CharlesB
  • 86,532
  • 28
  • 194
  • 218
Chris Noldus
  • 2,432
  • 2
  • 20
  • 27

3 Answers3

42

To force our widgets for a particular widget provider to be updated, we will need to do the following:

  • Set up our provider to receive a specialized broadcast
  • Send the specialized broadcast with:
    1. All the current widget Id's associated with the provider
    2. The data to send
    3. Keys that only our provider responds to (Important!)
  • Getting our widget to update on click (without a service!)

Step 1 - Set up our AppWidgetProvider

I will not go through the details of creating the info xml file or changes to the Android Manifest - if you don't know how to create a widget properly, then there are plenty of tutorials out there you should read first.

Here is an example AppWidgetProvider Class:

public class MyWidgetProvider extends AppWidgetProvider {

public static final String WIDGET_IDS_KEY ="mywidgetproviderwidgetids";
public static final String WIDGET_DATA_KEY ="mywidgetproviderwidgetdata";

}

@Override
public void onReceive(Context context, Intent intent) {
    if (intent.hasExtra(WIDGET_IDS_KEY)) {
        int[] ids = intent.getExtras().getIntArray(WIDGET_IDS_KEY);
        if (intent.hasExtra(WIDGET_DATA_KEY)) {
           Object data = intent.getExtras().getParcelable(WIDGET_DATA_KEY);
           this.update(context, AppWidgetManager.getInstance(context), ids, data);
        } else {
            this.onUpdate(context, AppWidgetManager.getInstance(context), ids);
        }
    } else super.onReceive(context, intent);
}

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
        int[] appWidgetIds) {
    update(context, appWidgetManager, appWidgetIds, null);
}

//This is where we do the actual updating
public void update(Context context, AppWidgetmanager manager, int[] ids, Object data) {

    //data will contain some predetermined data, but it may be null
   for (int widgetId : ids) {
        .
        .
        //Update Widget here
        .
        .
        manager.updateAppWidget(widgetId, remoteViews);
    }
}

Step 2 - Sending the broadcast

Here we can create a static method that will get our widgets to update. It is important that we use our own keys with the widget update action, if we use AppWidgetmanager.EXTRA_WIDGET_IDS we will not only break our own widget, but others as well.

public static void updateMyWidgets(Context context, Parcelable data) {
    AppWidgetManager man = AppWidgetManager.getInstance(context);
    int[] ids = man.getAppWidgetIds(
            new ComponentName(context,MyWidgetProvider.class));
    Intent updateIntent = new Intent();
    updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
    updateIntent.putExtra(MyWidgetProvider.WIDGET_ID_KEY, ids);
    updateIntent.putExtra(MyWidgetProvider.WIDGET_DATA_KEY, data);
    context.sendBroadcast(updateIntent);
}

If using this method with multiple providers MAKE SURE they use different keys. Otherwise you may find widget a's code updating widget b and that can have some bizarre consequences.

Step 3 - updating on click

Another nice thing to do is to get our widget to update magiacally whenever it is clicked. Add the following code into the update method to acheive this:

RemoteViews views = 
      new RemoteViews(context.getPackageName(),R.layout.mywidget_layout);

Intent updateIntent = new Intent();
updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
updateIntent.putExtra(myWidgetProvider.WIDGET_IDS_KEY, ids);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
      context, 0, updateIntent, PendingIntent.FLAG_UPDATE_CURRENT);

views.setOnClickPendingIntent(R.id.view_container, pendingIntent);

This code will cause the onUpdate method to be called whenever the widget is clicked on.

Notes

  • Any call to updateWidgets() will cause all instances of our widget to be updated.
  • Clicking the widget will cause all instances of our widget to be updated
  • NO SERVICE REQUIRED
  • Of course, be careful not to update to often - widget updates use battery power.
  • Bear in mind that the broadcast is received by ALL widget providers, and its our special keys that ensure it is only our widgets that actually update.
GoRoS
  • 5,183
  • 2
  • 43
  • 66
Chris Noldus
  • 2,432
  • 2
  • 20
  • 27
  • It should be noted that your don't HAVE to send data. In my app I just wanted to force an update so I'm not sending any data at all. – Chris Noldus Nov 29 '11 at 01:27
  • This answer inspired me in fixing a really nasty issue where widgets were going crazy on the TouchWiz launcher. Stuff like flashing random content, and then disappearing. That only happened to our widget though, others were just fine. If that happens to you: **DON'T** use `AppWidgetmanager.EXTRA_WIDGET_IDS`, even if it works on all other devices. This is likely something that has been fixed in AOSP and Samsung hasn't pulled in their code (yet). – rock3r Mar 27 '14 at 13:48
  • 1
    I think widget providers should decide what to do based on the Action in the intent and not the extras contained within. What you're doing in Step #2 is "ducktyping", if you had your own Action, you could use any kind of extra keys (even the built-in ones). – TWiStErRob Jun 29 '14 at 10:58
  • Note that if you only want to update all your AppWidgets without any other data, you can pass your provider name to AppWidgetManager, avoiding nasty issues about AppWidget Ids. See [this answer](http://stackoverflow.com/a/20483822/2420519). – Hai Zhang Aug 13 '14 at 07:45
  • @ChrisNoldus i tried this but it still doesn't refresh can you help me http://stackoverflow.com/questions/39388686/how-to-refresh-widget-listview-when-button-refresh-is-clicked – natsumiyu Sep 13 '16 at 03:46
  • I also suggest to add `updateIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);` to updateIntent if you need to update your widget ASAP. I discovered that time from updateIntent to onReceive() call is very long sometimes, because intents are handled by system queue. Adding that flag helped me a lot. https://stackoverflow.com/a/29145189/5093606 – Andriy Jul 27 '18 at 08:25
5

Sorry to the OP for a late answer, but for anyone else, you can use a method like this to update all your app widgets of a particular kind (layout and class):

public static void updateAllWidgets(final Context context,
                                    final int layoutResourceId,
                                    final Class< ? extends AppWidgetProvider> appWidgetClass)
{
    RemoteViews remoteViews = new RemoteViews(context.getPackageName(), layoutResourceId);

    AppWidgetManager manager = AppWidgetManager.getInstance(context);
    final int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(context, appWidgetClass));

    for (int i = 0; i < appWidgetIds.length; ++i)
    {
        manager.updateAppWidget(appWidgetIds[i], remoteViews);
    }
}

What this does is it creates a RemoteViews for your layout and it then gets a hold of the AppWidgetManager and finds all the app widgets for the class you provided, iterates over them and updates them.

xbakesx
  • 13,202
  • 6
  • 48
  • 76
  • 2
    I would just like to point out that whenever you update `RemoteViews` you are (or at least should think of it as) starting the `RemoteViews` over. If all you want to do is update the image, and leave all the other attributes in tact, you better (re)set all those other attributes as well or you will lose them. – xbakesx Apr 27 '13 at 02:06
-5

Any time you're dealing with Types it's pretty easy to simply work with statics. And, if you desire is for specific things to happen based on a "trigger" I'd always recommend event based programming ... Intents are essentially the default "event" in Android (for passing data in your event make sure the object implements the Parcelable interface).

There are many many examples of these concepts on the various forums and SDK documentation.

BonanzaDriver
  • 6,411
  • 5
  • 32
  • 35
  • 1
    You are right that Intents are the solution, however I have scoured the Internet and not found any examples of a good solution. I have raised this question because I found a way to do it and thought others would like to know - hence the fact that I have answered my own question. – Chris Noldus Nov 29 '11 at 01:25