http://www.developer.com/ws/android/programming/Creating-a-Home-Screen-App-Widget-on-Android-3833306.htm

Back to article

Creating a Home Screen App Widget on Android


August 6, 2009

Android users got their first taste of the App Widget when some basic controls like the Clock and Picture Frame Home Screen controls shipped with the first Android handsets. However, the App Widget API was not publicly available for developers to use until recently. This interface gives developers two new and interesting ways to provide Android application functionality outside the traditional boundaries of a phone application. Developers can use the App Widget API (released in Android 1.5) to create simple controls and craft new App Widget hosts to display and use these controls.

This article shows you how to create a Home Screen App Widget that updates at a user-configured time interval by using the AlarmManager interface. Specifically, you will see how to create an App Widget that displays an image chosen randomly from a set of images. The displayed image changes periodically according to a timing interval configured by the user.

Creating a simple App Widget involves several steps:

  1. Create a RemoteView, which provides the user interface for the App Widget.
  2. Tie the RemoteView to an Activity that implements the AppWidgetProvider interface.
  3. Provide key App Widget configuration information in the Android manifest file.

 


Visit the Android Dev Center

 

Preparing Your Project for an App Widget

An App Widget is basically just a BroadcastReceiver that handles specific actions. The AppWidgetProvider interface simplifies handling these actions by providing a framework for developers to implement the following methods:

  • onEnabled(): Called when the first App Widget is created. Global initialization should take place here, if applicable.
  • onDisabled(): The inverse of the onEnabled() method, called when the last App Widget handled by this definition is deleted. Global cleanup should take place here, if applicable.
  • onUpdate(): Called when the App Widget needs to update its View, which could be when the user first creates the widget.
  • onDeleted(): Called when a particular instance of this App Widget is deleted. Cleanup for the specific instance in question should take place here.
  • onReceive(): The default implementation of this method handles the BroadcastReceiver actions and makes the appropriate calls to the methods shown above. (Warning: A well-documented defect exists that requires the developer to handle certain cases explicitly. See the following note for more information.)
Author's Note: Check this link for more information about the current defects of the AppWidget framework. The discussion includes code to work around the issue (also found in the downloadable sample code for this article) and instances in which App Widget identifiers can be passed to the onUpdate() method even when they don't actually exist.

 

For the Android system to know about the App Widget, place a standard <receiver> tag in the Android manifest file. The following snippet shows an example:

<receiver android:name="ImagesWidgetProvider">
<intent-filter>
<action
android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/imageswidget_info" />
</receiver>

You'll notice that, unlike a typical <receiver> definition, a <meta-data> section references an XML file resource. This file defines addition data for App Widget, corresponding to the AppWidgetProviderInfo class. The information defined here is immutable, so, this example does not include the value for updatePeriodMillis, because this app lets users modify the timing between updates—which can't be done if you assign updatePeriodMillis here. The following code shows the complete imageswidget_info.xml file:

<?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:initialLayout="@layout/widget"
android:configure=
"com.mamlambo.imageswidget.ImagesWidgetConfiguration" />

The <appwidget-provider> tag defines the size of the App Widget, the default layout to use, and the configuration Activity to launch whenever an instance of the App Widget is created. To display nicely on the Home screen, widgets must adhere to certain size guidelines. The Home screen is divided into cells of a particular size. The basic formula (helpfully provided by Google) is to multiply the number of cells you want to occupy by 74, and then subtract 2. In this case, the App Widget should be square: two cells high and two cells wide. Therefore, use a size of ((74*2)-2), or 146.

Implementing onUpdate()

An App Widget that doesn't display anything isn't very useful. Luckily, the implementation for the RemoteView object of this App Widget is straightforward; it uses a set of images stored in the drawable resource directory of the application. The application references these image resources as R.drawable.[imagename]. The code creates an array holding the names, which makes it easy to randomly draw one of the images. The following snippet shows the onUpdate() implementation, which randomly draws one of the included images:


@Override
public void onUpdate(Context context,
   AppWidgetManager appWidgetManager,
   int[] appWidgetIds) {
   for (int appWidgetId : appWidgetIds) {
      int imageNum = (new
         java.util.Random().nextInt(IMAGES.length));
      RemoteViews remoteView = new
         RemoteViews(context.getPackageName(),
         R.layout.widget);
      remoteView.setImageViewResource(
         R.id.image, IMAGES[imageNum]);
      appWidgetManager.updateAppWidget(
         appWidgetId, remoteView);
   }
}

Note that the onUpdate() method expects a list of App Widget instances as the last parameter. Each instance must be handled separately. Due to existing defects with the App Widget framework, some instances provided may not be visible or enabled; however you can safely ignore the problem for this example. Bear in mind that your particular implementation may want to track which App Widgets are actually active.

You can review the simple R.layout.widget XML layout definition within the included code download; it is basically just an ImageView. RemoteViews can use only a limited set of View objects, including Button, ImageButton, ImageView, TextView, AnalogClock, Chronometer, and ProgressBar—and only within FrameLayout, LinearLayout, or RelativeLayout. Keep the RemoteView simple, because access is controlled through such methods as setImageViewResource() and setTextViewText(). Their purpose is to draw a View within another process, thus your application has less control over them than a normal layout.

At this point, the basics of the App Widget are complete. However, to allow users to configure the time between image updates, you must implement the configuration Activity and then handle scheduling of the RemoteView updates.

Implementing the App Widget Configuration Activity

The configuration Activity, defined in the manifest file as ImagesWidgetConfiguration, is like any Activity, with two exceptions:

  1. When launched, it's intended to return a result, so the implementation must always call the setResult() method with an appropriate result (either RESULT_CANCELED or RESULT_OK).
  2. When setting the result, the App Widget identifier value must be placed in the extra referenced by AppWidgeManager.EXTRA_APPWIDGET_ID.

The following snippet of code demonstrates dealing with both exceptions, calling setResult() and setting the default result as RESULT_CANCELED in case the user backs out of the Activity:

Bundle extras = launchIntent.getExtras();
if (extras != null) {
   appWidgetId = extras.getInt(
      AppWidgetManager.EXTRA_APPWIDGET_ID,
      AppWidgetManager.INVALID_APPWIDGET_ID);
   Intent cancelResultValue = new Intent();
   cancelResultValue.putExtra(
      AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
   setResult(RESULT_CANCELED, cancelResultValue);
}

Aside from those two restrictions, you may implement the configuration Activity however you like. Figure 1 shows the simple configuration Activity for this example. If you return RESULT_CANCELED, the App Widget won't be displayed to the user. If you return RESULT_OK, it will be. You can use any storage mechanism you prefer for saving configuration data for this particular instance of the App Widget. The example uses the SharedPreferences interface, storing the time between updates with the particular App Widget identifier. You can see the full implementation in the downloadable code for this article.


Figure 1. Configuration Screen:
This simple configuration screen lets users set the time interval between image display changes for this App Widget.

Implementing the Update Scheduling

As mentioned earlier, the configuration values within the AppWidgetProviderInfo class are immutable. Because the updateTimeMillis value is within this class, any App Widget instance created with an AppWidgetProviderInfo that has this value set will update at that frequency, with no way to change it. Because this application should let users configure the frequency at which the images are displayed within each App Widget instance, you must implement the functionality yourself.

The AlarmManager class is the most logical update mechanism to use, because it lets supports recurring notifications. These notifications are simple PendingIntent objects that will be triggered.

You might think that you can just create an Intent object with the action value of AppWidgetManager.ACTION_APPWIDGET_UPDATE and set the extras value to the particular App Widget identifier. Then you would simply schedule it to update repeatedly by calling the AlarmManager's setRepeating() method. Unfortunately, this does not work. The Android system reuses Intents that match both action and scheme values—the "extras" values are not compared. In practice, this means the Intent for each App Widget identifier would actually be the same Intent. Fortunately, the solution is straightforward: define a scheme for your App Widget and use it to define unique Intent instances. The following code snippet demonstrates how to do this:

Intent widgetUpdate = new Intent();
widgetUpdate.setAction(
   AppWidgetManager.ACTION_APPWIDGET_UPDATE);
widgetUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, 
   new int[] { appWidgetId });
// make this pending intent unique
widgetUpdate.setData(
   Uri.withAppendedPath(Uri.parse(
   ImagesWidgetProvider.URI_SCHEME + "://widget/id/"), 
   String.valueOf(appWidgetId)));
PendingIntent newPending = PendingIntent.getBroadcast(
    getApplicationContext(), 0, widgetUpdate, 
    PendingIntent.FLAG_UPDATE_CURRENT);
// now schedule it
AlarmManager alarms = (AlarmManager) getApplicationContext().
   getSystemService(Context.ALARM_SERVICE);
alarms.setRepeating(AlarmManager.ELAPSED_REALTIME, 
   SystemClock.elapsedRealtime(), updateRateSeconds * 1000, 
   newPending);

You'll find the preceding code in the ImagesWidgetConfiguration Activity. The manifest file shows the <receiver> block for handling this particular scheme. You will also need to halt the repeating alarm within onDeleted() method in the AppWidgetProvider interface. Finally, when the handset restarts, update scheduling must be restarted as well.


Figure 2. Multiple Widget Instances:
This figure shows two different instances of the final App Widget running at the same time.

A straightforward solution to this involves leveraging some logic directly within the AppWidgetProvider's onReceive() method. First, check for the update action. If it's an update, ensure that it doesn't contain a scheme value. If it does not, then the PendingIntent, scheduled with the AlarmManager, did not trigger this action. In that case, check to see if there is a configured preference value for each of the App Widget identifiers. If not, then you know this update action was received before the App Widget has been configured. However, if the preference value is available, then you know you can schedule the PendingIntent. Again, you can see this full logic implemented within the onReceive() method.

This scheme allows multiple App Widgets of a given type to be displayed simultaneously, as shown in Figure 2. The updates can occur at different frequencies, and each displayed image is selected randomly from the included set.

You've seen how to create a basic App Widget on the Android platform. Each instance of this App Widget (shown on the Home screen) runs on a separate schedule, configured by the user, even though that capability is not available directly through the App Widget framework. A future article will show you how to expand the functionality of this App Widget to use images downloaded from the Internet, and explain how to enhance it with an on-screen interface to control the display of images directly.

About the Authors

Shane Conder is a software developer focused on mobile and web technologies. He currently works at a small mobile software company. With almost two decades of experience in software production, Lauren Darcey specializes in the development of commercial grade mobile applications. Recently, Shane and Lauren coauthored an in-depth programming book entitled Android Wireless Application Development, available from Addison-Wesley (ISBN: 0321627091). They can be via their blog.

Sitemap | Contact Us