xenonite
xenonite

Reputation: 1671

Can't update android widget from BroadcastReceiver

I want to code an app that displays some information in an widget, which should be updated from time to time. From time to time means, that I use an alarm timer to trigger a peroidic update. So heres the problem: intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); is null for the broadcast receivers intent.

Here's my widget provider:

public class MyWidgetProvider extends AppWidgetProvider {
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;      
    for(int i=0; i<N; i++) {
            int widgetId = appWidgetIds[i];         
            RemoteViews rViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);         
            Intent intent = new Intent(context.getApplicationContext(), TrafficService.class);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);            
            rViews.setTextViewText(R.id.TextView01, "" + System.currentTimeMillis());           
            appWidgetManager.updateAppWidget(widgetId, rViews);
        }
    }
}

and this is the BroadcastReceiver causing the problem:

public class TimeIntervalReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
// set new alarm timer (code cut out)       
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context.getApplicationContext());
        // PROBLEM BELOW!
        int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);     
        if(appWidgetIds == null) Log.d("TRAFFIC", "oh SHIT");
        if(appWidgetIds != null && appWidgetIds.length > 0) {
            for(int widgetId : appWidgetIds) {
                RemoteViews rViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
                rViews.setTextViewText(R.id.TextView01, "some data");
                appWidgetManager.updateAppWidget(widgetId, rViews);
            }
        }
    }
}

Is this even solveable?

Upvotes: 4

Views: 4838

Answers (4)

Wondering: Why don't you just override the onReceive() method of the WidgetProvider? Since AppWidgetProvider is extended from BroadcastReceiver, this is perfectly legal as long as you call super.onReceive().

The Intent you get through onReceive() contains the Widget Ids as an Extra, if it's been called by the AppWidgetHost (Launcher). If you call it by yourself, you have to add the required extras by yourself.

This looks to be an elegant way to trigger the WidgetProvider from any other activity while keeping the original functionality of it.

Remember: AppWidgetProvider is a convenient class to easy the development of Widgets but to the core it's just a BroadcastReceiver.

I solved it like this:

public class WidgetProvider extends AppWidgetProvider {

    public static final String ACTION_RESTART_SERVICE = "ACTION_RESTART_SERVICE";

    @Override
    public void onReceive(Context context, Intent intent) {
    Log.d(TAG, "onEnabled() called.");
    if (intent.getAction() != null && intent.getAction().equals(WidgetProvider.ACTION_RESTART_SERVICE))
    {
        // Start service
        Log.d(TAG, "ACTION_RESTART_SERVICE... Restarting service with designated update interval...");
        Intent i = new Intent(context, UpdateWidgetService.class);
        service = startService(i, context, service);
    } else
    {
        // Other intent, call super class
        Log.d(TAG, "Not ACTION_RESTART_SERVICE... calling superclass onReceive()...");
        super.onReceive(context, intent);
    }
}
}

And in your Activity/Fragment:

    /**
 * Restart the update service via WidgetProvider to reflect new profile and settings
 * @param context Context is required
 */
private void restartService(Context context)
{
    Intent intent =  new Intent(context,
            WidgetProvider.class);
    intent.setAction(WidgetProvider.ACTION_RESTART_SERVICE);

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
    {
        // Send intents to all widget provider classes
        intent.setClass(context, WidgetProviderSize1.class);
        getActivity().sendBroadcast(intent);
        intent.setClass(context, WidgetProviderSize2.class);
        getActivity().sendBroadcast(intent);
        intent.setClass(context, WidgetProviderSize3.class);
        getActivity().sendBroadcast(intent);
        intent.setClass(context, WidgetProviderSize4.class);
        getActivity().sendBroadcast(intent);
        intent.setClass(context, WidgetProviderSize5.class);
        getActivity().sendBroadcast(intent);

    } else
        getActivity().sendBroadcast(intent);

}

Looks a bit complicated because I have a dynamic resizable widget with JellyBean and fixed Widget sizes below that OS version, but the solution should be clear.

An even easier solution might be to just send an android.appwidget.action.APPWIDGET_UPDATE broadcast intent exactly like a Launcher would to trigger the onUpdate() of your WidgetProvider directly.

Then there is a totally different option available: Let the Updateservice get the WidgetIDs all by himself, so there is no need to get them from the update intent. This is ok if all widgets basicly share the same configuration and should all be updated if anything changes in configuration:

    /**
 * Get all Widget IDs of WidgetProviders used by this app
 * @param appWidgetManager AppWidgetManager to use
 * @return Array of widget IDs
 */
private int[] getAppWidgetIDs(AppWidgetManager appWidgetManager)
{
    int[] widgetIdsOfOneProviderSize1 = getAppIdsOfSingleProvider(appWidgetManager, WidgetProviderSize1.class);
    int[] widgetIdsOfOneProviderSize2 = getAppIdsOfSingleProvider(appWidgetManager, WidgetProviderSize2.class);
    int[] widgetIdsOfOneProviderSize3 = getAppIdsOfSingleProvider(appWidgetManager, WidgetProviderSize3.class);
    int[] widgetIdsOfOneProviderSize4 = getAppIdsOfSingleProvider(appWidgetManager, WidgetProviderSize4.class);
    int[] widgetIdsOfOneProviderSize5 = getAppIdsOfSingleProvider(appWidgetManager, WidgetProviderSize5.class);
    int[] widgetIdsOfOneProvider = getAppIdsOfSingleProvider(appWidgetManager, WidgetProvider.class);
    int allWidgetIds[] = new int[widgetIdsOfOneProviderSize1.length + widgetIdsOfOneProviderSize2.length
                                 + widgetIdsOfOneProviderSize3.length + widgetIdsOfOneProviderSize4.length
                                 + widgetIdsOfOneProviderSize5.length + widgetIdsOfOneProvider.length];
    int index = 0;
    for (int id : widgetIdsOfOneProviderSize1)
    {
        allWidgetIds[index] = id;
        index ++;
    }
    for (int id : widgetIdsOfOneProviderSize2)
    {
        allWidgetIds[index] = id;
        index ++;
    }
    for (int id : widgetIdsOfOneProviderSize3)
    {
        allWidgetIds[index] = id;
        index ++;
    }
    for (int id : widgetIdsOfOneProviderSize4)
    {
        allWidgetIds[index] = id;
        index ++;
    }
    for (int id : widgetIdsOfOneProviderSize5)
    {
        allWidgetIds[index] = id;
        index ++;
    }

    for (int id : widgetIdsOfOneProvider)
    {
        allWidgetIds[index] = id;
        index ++;
    }

    return allWidgetIds;
}

private int[] getAppIdsOfSingleProvider(AppWidgetManager appWidgetManager, Class cls)
{
    ComponentName thisWidget = new ComponentName(getApplicationContext(),
            cls);
    int[] widgetIdsOfOneProvider = appWidgetManager.getAppWidgetIds(thisWidget);
    return widgetIdsOfOneProvider;
}

Yes, I should've used ArrayUtils to put the arrays together... leaves room for improvement ;-)

Upvotes: 0

Waza_Be
Waza_Be

Reputation: 39538

Lol, I have my answer...

Simply replace:

int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

by

ComponentName thisWidget = new ComponentName(getApplicationContext(),MyWidgetProvider.class);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);

Upvotes: 8

xenonite
xenonite

Reputation: 1671

so i changed the broadcast receiver to the following:

public class TimeIntervalReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND, Config.UPDATE_RATE);     
        Date d = new Date(c.getTimeInMillis());

        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context.getApplicationContext());
        int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);

        if(appWidgetIds == null) Log.d("TRAFFIC", "oh SHIT");  // triggers :(
        if(appWidgetIds != null && appWidgetIds.length > 0) {
            for(int widgetId : appWidgetIds) {
                RemoteViews rViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
                rViews.setTextViewText(R.id.TextView01, "someupdateddata");
                appWidgetManager.updateAppWidget(widgetId, rViews);
            }
        }
        Intent i = new Intent(context, TimeIntervalReceiver.class);
        i.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); // here i'd add the existing widget ids...
        PendingIntent sender = PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);

        AlarmManager aManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        aManager.set(AlarmManager.RTC, d.getTime(), sender);
}
}

this still triggers - because the first alarm timer is (needs to be) set without knowing the widget-id-array. (e.g. by an onclickhandler)... is there any way to do this right?

Upvotes: 0

Jong
Jong

Reputation: 9115

When the system calls your implementation of AppWidgetProvider, it fills the intent with these extras.

But when it calls your broadcast receiver which has nothing to do with the widget, it does not fill this extra in the intent.

You will have to use another method to transfer the ID's. Maybe you could fill them in the Intent which is fired as the alarm fires?

Upvotes: 0

Related Questions