Skip to content

September 29, 2012

1

How To Make an Android Home Screen Widget Update Only When Visible

Depending on the nature of your home screen widget, you may want it to stay closely synched to real-time data. It would be nice if Android fired a system event that you could register for whenever your widget is visible on the screen. However, Android does not do this, and could not be counted upon to do so in any case because some users may be using a 3rd party alternative to the default home screen.

You’d like to update your widget early and often, at times when the user might be looking at it. But you want to prevent it from updating (and wasting resources) when the user is not engaged. There is a way to achieve this. This technique isn’t perfect, because it will cause your widget to update even when hidden behind another running app, but that much is unavoidable. (Note to Google: an isWidgetVisible() method would be great!)

I’m going to describe how I used a Service to update my web community widget only when the screen is on.  The java classes involved are:

  • MRPWidget (extends AppWidgetProvider)
  • WidgetUpdate (extends IntentService)
  • MRPAlarmReceiver (extends BroadcastReceiver)

I described the MRPWidget class here: creating an Android home screen widget.  Now I will describe how the Service is used to update the widget.

The first step is to configure the MRPWidget in the Android manifest to listen for the USER_PRESENT system event. This is an Intent that gets fired when the user unlocks the screen.

<receiver android:name="com.kingdom.android.widget.MRPWidget"
  android:label="MRP Widget">
  <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    <action android:name="android.intent.action.USER_PRESENT"/>
  </intent-filter>
  <meta-data
    android:name="android.appwidget.provider"
    android:resource="@xml/mrp_widget_info" />
</receiver>

Next, I overrode onReceive in my MRPWidget class to respond to this Intent:

@Override
public void onReceive(Context context, Intent intent) {
    // call to super.onReceive to delegate other widget intents
    super.onReceive(context, intent);

    // make sure the user has actually installed a widget
    // before starting the update service
    int widgetsInstalled = widgetsInstalled(context);
    if (widgetsInstalled != 0 && intent.getAction().equals(Intent.ACTION_USER_PRESENT)) {
        startWidgetUpdateService(context);
    }
}

// convenience method to count the number of installed widgets
private int widgetsInstalled(Context context) {
    ComponentName thisWidget = new ComponentName(context, MRPWidget.class);
    AppWidgetManager mgr = AppWidgetManager.getInstance(context);
    return mgr.getAppWidgetIds(thisWidget).length;
}

Now, whenever the phone screen is unlocked, this widget will ensure its update service is started.

In order to time the recurring updates, I used Android’s AlarmManager and created a corresponding BroadcastReceiver to listen for the alarms. Here is the entirety of this receiver:

public class MRPAlarmReceiver extends BroadcastReceiver {

    public static final String ACTION_REFRESH_MRP_ALARM =
        "com.kingdom.android.widget.ACTION_REFRESH_MRP_ALARM";

    @Override
    public void onReceive(Context context, Intent intent) {
        MRPWidget.startWidgetUpdateService(context);
    }
}

I decided to create an IntentService to do the widget updates, for two reasons: 1) potentially slow operations, such as connecting to a remote server, should be delegated to service, which runs in the background; 2) IntentService handles its own threading and lifecycle, making it a simpler alternative to the regular Service class.

My widget updating IntentService class is called, unimaginatively, “WidgetUpdate”. In the onCreate method, I schedule the repeating alarm. I specify AlarmManager.ELAPSED_REALTIME, which will not wake the device. I also added a method to cancel the alarm.

public static int UPDATE_FREQUENCY_SEC = 90;

@Override
public void onCreate() {
    super.onCreate();
    AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

    Intent alarmIntent = alarmIntent();

    long timeToRefresh = SystemClock.elapsedRealtime() + UPDATE_FREQUENCY_SEC * 1000;
    alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME,
                                     timeToRefresh,
                                     UPDATE_FREQUENCY_SEC * 1000,
                                     alarmIntent);
}

protected void cancel() {
    PendingIntent alarmIntent = alarmIntent();

    AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    alarmManager.cancel(alarmIntent);
}

// method to construct the alarm intent
private PendingIntent alarmIntent() {
    Intent intentToFire = new Intent(MRPAlarmReceiver.ACTION_REFRESH_MRP_ALARM);
    PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 0, intentToFire, 0);

    return alarmIntent;
}

As you can see, the alarm frequency is hard-coded to 90 seconds. Ideally, this should be enhanced to allow the user to customize the frequency.

The last step is to override the onHandleIntent method, which does the actual work of performing the update:

@Override
protected void onHandleIntent(Intent intent) {

    // though I've set the AlarmManager not to wake the device,
    // for extra safety I test if the screen is off here
    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);

    if (!pm.isScreenOn()) {
        cancel();
    }
    else {
        // ** CODE OMITTED **
        // This is where the remote call is made to the server,
        // the data is downloaded and parsed.
        // The details here aren't relevant to the example.

        // At this point I have the data stored in objects,
        // which I pass to my AppWidgetProvider class:
        MRPWidget widget = new MRPWidget();
        widget.updateMRP(this, mrps, activeMembers, pmAlert, replyAlert);
    }
}

And we’re done. Now the widget will update only when the screen is on and unlocked. Updates will suspend when the screen is off. In this way, the widget will display the most current data to the user, without unnecessarily consuming the limited resources of the phone.

Other considerations: data usage may be a concern to your users. It is important to keep the amount of data sent by the server as small as possible–under 1 kilobyte per update, ideally, as some carriers round up to the nearest kilobyte. But beyond that, consider adding a user preference to slow down or stop the widget updates unless a wifi connection is present.

Read more from Android Development
1 Comment Post a comment
  1. Parmesh
    Nov 5 2014

    what is the value of MRPAlarmReceiver.ACTION_REFRESH_MRP_ALARM?

    Reply

Share your thoughts, post a comment.

(required)
(required)

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments