There are two scenarios where our AppWidget may be configured. The first is right after the user requests its creation, and the second is when the user taps on an existing widget instance on the home screen.
Generally speaking, this Activity operates just as any other Activity you’ve expe- rienced throughout the book. It inflates a layout, gets references to the various Views, and responds to user input. Only a couple of items are worthy of highlighting here, but they’re important details that you must implement.
Let’s start with how Android knows which Activity to launch after a new instance is created. To do that, we’ll take a brief side trip to look at the metadata related to this AppWidget.
17.7.1 AppWidget metadata
Earlier we alluded to a special metadata file that defines attributes for an AppWidget. This file is associated with a specific receiver entry in the AndroidManifest.xml file.
Within the metadata file, you can associate a specific Activity as the preferred con- figuration tool. The following listing presents the sitemonitorwidget.xml file. Even though our focus in this section is on the Activity, this is a good opportunity to tie together a couple of ideas you’ve learned to this point.
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minHeight="72dp"
android:minWidth="72dp"
android:initialLayout="@layout/monitor"
android:updatePeriodMillis="0"
android:configure=
"com.msi.unlockingandroid.sitemonitor.SiteMonitorConfigure"
android:icon="@drawable/icon"
>
</appwidget-provider>
Earlier in this chapter we described the screen real estate consumed by an AppWidget. The height and width are specified as minimum values. Each of the available spaces or cells in the screen is 74 pixels square. The formula for deriving the values here is the number of cells requested times 74 minus 2.
The initial layout used by the widget is defined in the initialLayout attribute. At runtime the application is free to change the layout, but you should consider the fact that by the time your widget is ready for updating, it’s already been placed on the screen, so your expectations might be shattered if you were hoping to bump some other widget out of the way!
The updatePeriodMillis specifies the update interval. Based on the architecture of the SiteMonitor, this has little importance, so set it to 0 to tell the widget not to bother waking itself up to update. Setting this attribute to a nonzero value causes the device to wake up periodically and call the onUpdate() method in the AppWidget- Provider implementation.
Finally, you see the configure attribute, which permits you to specify the fully qual- ified class name for the Activity to be launched when the user selects this widget from the list of available widgets on the home screen. When the user is selecting from the list of widgets, the icon displayed in the list is defined by the icon attribute.
Now that the Activity is associated with our AppWidget, it’s time to examine the key elements of the Activity. The full code is available for download. The snippets shown here are only the portions particularly relevant to AppWidget interactions.
Listing 17.6 AppWidget metadata file defining widget characteristics
17.7.2 Working with Intent data
When the AppWidget’s configuration Activity is launched, the most important piece of information is the associated widget identifier. This value is stored as an extra in the Intent and should be extracted during the onCreate() method. The following listing demonstrates this technique.
@Override
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.main);
etSiteName = (EditText) findViewById(R.id.etSiteName);
etSiteURL = (EditText) findViewById(R.id.etSiteURL);
etSiteHomePageURL = (EditText) findViewById(R.id.etSiteHomePageURL);
tvSiteMessage = (TextView) findViewById(R.id.tvSiteMessage);
final Button btnSaveSite = (Button) findViewById(R.id.btnSaveSite);
btnSaveSite.setOnClickListener(this);
final Button btnVisitSite = (Button) findViewById(R.id.btnVisitSite);
btnVisitSite.setOnClickListener(this);
widgetId =
getIntent().getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,widgetId);
// lookup to see if we have any info on this widget smm = SiteMonitorModel.getWidgetData(this, widgetId);
if (smm != null) {
etSiteName.setText(smm.getName());
etSiteURL.setText(smm.getUrl());
etSiteHomePageURL.setText(smm.getHomepageUrl());
tvSiteMessage.setText(smm.getMessage());
} }
The Activity looks like boilerplate code, as it begins with wiring up the various view elements in the layout to class-level variables B. The widgetId is extracted from the startingIntent C. Again you see the relationship between widgetId and widget- specific data managed by the SiteMonitorModel class D. If data is available, the GUI elements are prepopulated with the values E. This scenario would only come into play after the widget has been successfully created and subsequently clicked for man- aging it.
At this point, the Activity operates as expected, permitting the user to update the details of the widget data as well as visit the associated website.
Listing 17.7 Setting up the configuration Activity to manage a widget instance
B
Wire up GUI
C
Extract widget identifier
Look up widget data
D
Populate GUI
E
17.7.3 Confirming widget creation
When the user has populated the required fields and clicks the Save button, you need to not only save the data via the SiteMonitorModel class but also let the AppWidget infrastructure know that you’ve affirmed the creation of this widget instance. This takes place by using the Activity’s setResult() method along with an Intent con- taining an extra indicating the widget number. In addition, you want to ensure that the alarm is enabled for future updates. Finally, you really don’t want to wait until the next alarm interval elapses; you want to get an update now. The following listing dem- onstrates how to accomplish each of these tasks.
public void onClick(View v) { switch (v.getId()) { case R.id.btnSaveSite: { saveInfo();
// update the widget's display
SiteMonitorWidgetImpl.UpdateOneWidget(v.getContext(), widgetId);
// let the widget provider know we're done and happy Intent ret = new Intent();
ret.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,widgetId);
setResult(Activity.RESULT_OK,ret);
// let's ask for an update and also enable the alarm Intent askForUpdate =
new Intent(SiteMonitorBootstrap.ALARM_ACTION);
this.sendBroadcast(askForUpdate);
SiteMonitorBootstrap.setAlarm(this);
finish();
} break;
case R.id.btnVisitSite: { saveInfo();
Intent visitSite = new Intent(Intent.ACTION_VIEW);
visitSite.setData(Uri.parse(smm.getHomepageUrl()));
startActivity(visitSite);
} break;
} }
When the user clicks the Save button (or the Visit Site button), the widget-specific data is saved B. There’s nothing fancy there—just a call to SiteMonitor- Model.saveWidgetData(). The AppWidget subsystem is supposed to update the UI of the widget after the Configuration dialog box completes successfully, but experience shows that this isn’t always the case. Therefore a call is made to SiteMonitorWidget- Impl.UpdateOneWidget with the newly created widgetIdC.
Listing 17.8 Handling button clicks in the configuration Activity
Save data
B Update C
widget’s UI
D
Acknowledge widget creation Set alarm
E
Call setAlarm
F
Save data
B
Visit site home page
G
An important step in the life of a new AppWidget is to be sure to set the Activity result to RESULT_OKD, passing along an Intent extra that identifies the new widget by number.
At this point our new widget is populated with a name and no meaningful status information. To force an update, we broadcast an Intent that simulates the condition where the alarm has just triggered E. We also want to ensure that the alarm is armed for a subsequent operation, so we call SiteMonitorBootstrap.setAlarm()F.
In the event that the Visit Site button is clicked, we want to take the user to the defined home page of the currently active site being monitored by the widget G.
The last condition to handle is the case where the widget has been selected from the Add New Widget list on the home screen when the user cancels out of the config- uration activity. In this case the widget shouldn’t be created. To achieve this result, you set the result of the Activity to RESULT_CANCELED as shown in this listing.
public void onDestroy() { super.onDestroy();
if (!isFinishing()) { Intent ret = new Intent();
ret.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,widgetId);
setResult(Activity.RESULT_CANCELED,ret);
}
This code overrides the onDestroy() method. If this method is invoked for any reason other than finish() being called by the Activity itself, we want to cancel the Activity. This is accomplished with a call to setResult() with the inclusion of the Intent extra to pass along the widget identifier. Note that this cancel step is only required when the widget instance is first created. There’s no harm in setting the result for future invocations of the Activity.
At this point our AppWidget is created, you know how to store data, and you know how to configure a particular instance of the SiteMonitor widget. What’s needed next is to update the data. For that, we’ll look at the SiteMonitorService.