Chapter    21

Home Screen Widgets

Home screen widgets in Android present frequently changing information on the home screen of Android. Home screen widgets are disconnected views displayed on the home screen. Data content of these views is updated at regular intervals by background processes or just kept as a static view.

For example, an e-mail home screen widget might alert you to the number of outstanding e-mails to be read. The widget may just show you the number of e-mails and not the messages themselves. Clicking the e-mail count may then take you to the activity that displays actual e-mails. These could even be external e-mail sources such as Yahoo, Gmail, and Hotmail, as long as the device has a way to access the counts through HTTP or other connectivity mechanisms.

In the Android SDK a widget is declaratively defined. A widget definition contains the following:

  • A view layout to be displayed on the home screen, along with how big it should be to fit on a home page.
  • A timer that specifies the frequency of updates.
  • A broadcast receiver Java class called a widget provider that can respond to timer updates in order to alter the view in some fashion by populating with data.
  • An activity class that is responsible for collecting the input necessary to further configure the widget to be displayed.

The timer, the receiver, and the configuration activity are optional. Once a widget is defined and the Java classes are provided, the widget will be available for the user to drag onto a home page. The view and the corresponding Java classes are architected in such a way that they are disconnected from each other. For example, any Android service or activity can retrieve the view using its layout ID, populate that view with data (just like populating a template), and send it to the home screen. Once the view is sent to the home screen, it is dislodged from the underlying Java code.

Before we show you how to implement a widget, we’ll first give you an overview of how a widget is used by an end user.

User Experience with Home Screen Widgets

Home screen widget functionality in Android allows you to choose a preprogrammed widget to be placed on the home screen. When placed, the widget will allow you to configure it using an activity (defined as part of the widget package), if necessary. It is important to understand this interaction before actually going into the details of how a widget is implemented.

We are going to walk you through a widget called Birthday Widget that we have created for this chapter. We will present the source code for it later in the chapter. First, we are going to use this widget as an example for our walkthrough. As a consequence of source code coming later, we need your consideration to read along and follow the pictures and not look for this widget on your screen. If you follow the provided figures and explanation, you will know the nature and behavior of the Birthday Widget, which will make things clear when we code it subsequently.

Let’s start this tour by locating the widget we want and creating an instance of it on the home screen. The way you access the available widget list is different depending on the Android release. Usually though, the list of widgets is kept alongside the list of applications available on your device. Here is an example from API 16 (or Jellybean version of Android) in Figure 21-1.

9781430246800_Fig21-01.jpg

Figure 21-1. Home screen widget pick list

In the list of widgets in Figure 21-1, the Birthday Widget is designed for this chapter. If you choose this widget, Android allows you to drag it to one of pages of your home screen. Android will create a corresponding widget instance on the home screen that looks like the example Birthday Widget shown in Figure 21-2.

9781430246800_Fig21-02.jpg

Figure 21-2. An example Birthday Widget

Birthday Widget in Figure 21-2 will indicate in its header the name of the person, how many days away this person’s birthday is, when the date of birth falls this year, and a link to buy gifts. You may be wondering how the name of the person and date of birth were configured. What if you want two instances of this widget, each with the name and date of birth for a different person? This is where the widget configuration activity comes into play and is the topic we are covering next.

Understanding Widget Configuration Activity

A widget definition optionally includes a specification of an activity called a widget configuration activity. When you choose a widget from the home page widget pick list to create the widget instance, Android invokes the corresponding widget configuration activity if one is defined for it. This activity is something you need to code.

In case of our BirthdayWidget, this configuration activity will prompt you for the name of the person and the upcoming birth date, as shown in Figure 21-3. It is the responsibility of the configuration activity to save this information in a persistent place so that when an update is called on the widget provider, the widget provider will be able to locate this information and update the number of days until the birthday.

9781430246800_Fig21-03.jpg

Figure 21-3. Birthday Widget configuration activity

Note  When a user chooses to create two Birthday Widget instances on the home screen, the configuration activity will be called twice (once for each widget instance).

Internally, Android keeps track of the widget instances by allocating them unique IDs. This unique widget instance ID is passed to the Java callbacks and to the configurator Java class so that initial configuration and updates can be directed to the right instance of the widget on the homepage. In Figure 21-2, in the later part of the string satya:3, the 3 is the widget instance ID.

Understanding the Life Cycle of a Widget

The life cycle of a widget has the following phases:

  1. Widget definition
  2. Widget instance creation
  3. onUpdate() (when the time interval expires)
  4. Responses to clicks (on the widget view on the home screen)
  5. Widget deletion (from the home screen)
  6. Uninstallation

We will go through these phases in detail now.

Understanding Widget Definition Phase

Widget definition starts with the definition of the widget provider class in the Android manifest file. Listing 21-1 shows the definition for the AppWidgetProvider that we have designed for this chapter called BDayWidgetProvider in the manifest file.

Listing 21-1. Widget Definition in Android Manifest File

<!-- filename: AndroidManifest.xml, project: ProAndroid5_ch21_TestWidgets.zip -->
<manifest..>
<application>
....
   <receiver android:name=".BDayWidgetProvider">
      <meta-data android:name="android.appwidget.provider"
             android:resource="@xml/bday_appwidget_provider" />
      <intent-filter>
           <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
      </intent-filter>
   </receiver>
   ...
   <activity>
      .....
   </activity>
</application>
</manifest>

This definition indicates that there is a broadcast receiver Java class called BDayWidgetProvider which receives application widget broadcast update messages. The widget class definition in Listing 21-1 also points to an XML file @xml/bday_appwidget_provider which is /res/xml/bday_appwidget_provider.xml. This XML file is in Listing 21-2. This widget definition file has a number of things about this widget such as its layout resource file, update frequency, etc.

Listing 21-2. Widget View Definition in Widget Provider Information XML File

<!-- /res/xml/bday_appwidget_provider.xml(ProAndroid5_ch21_TestWidgets.zip) -->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="150dp"
    android:minHeight="120dp"
    android:updatePeriodMillis="43200000"
    android:initialLayout="@layout/bday_widget"
    android:configure="com.androidbook.BDayWidget.ConfigureBDayWidgetActivity"
    android:resizeMode="horizontal|vertical"
    android:previewImage="@drawable/some_preview_image_icon"
    >
</appwidget-provider>

This XML file is called the App widget provider information file. Internally, this gets translated to the AppWidgetProviderInfo Java class. This file identifies the width and height of the layout to be 150dp and 120dp, respectively. This definition file also indicates the update frequency to be 12 hours translated to milliseconds. The widget definition also points to a layout file through the initialLayout attribute. This layout file (see future Listing 21-6) produces the widget look that is shown in Figure 21-2.

Understanding resize Mode Attribute

Starting with SDK 3.1, users have the ability to resize a widget that is placed on one of their images. The user sees resize handles when they long-click the widget and can then use these handles to resize. This resize can be horizontal, vertical, or none. You can combine horizontal and vertical to resize the widget in both dimensions, as shown in Listing 21-2. However, to take advantage of this, your widget controls should be laid out in such a way that they can expand and contract using their layout parameters. There is no callback to tell you what size your widget is.

Understanding previewImage Attribute

The preview image attribute in Listing 21-2 indicates what image or icon is used to show your widget in the list of available widgets. If you omit it, the default behavior is to show the main icon for your application package, which is indicated in the manifest file.

Understanding Widget Layout: initialLayout Attribute

The layout for widget views is restricted to contain only certain types of view elements. The views allowed in a widget layout are exposed through an interface called RemoteViews, and only certain views can be composed into this layout. Some of the allowed view elements are shown in Listing 21-3. Note that their subclasses are not supported—only those that are included in Listing 21-3.

Listing 21-3. Allowed View Controls in RemoteViews

FrameLayout
LinearLayout
RelativeLayout
GridLayout
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper

This list may grow with each release. The primary reason for restricting what is allowed in a remote view is that these views are disconnected from the processes that actually control them. These widget views are hosted by an application like the Home application. The controllers for these views are background processes that get invoked by timers. For this reason, these views are called remote views. There is a corresponding Java class called RemoteViews that allows access to these views. In other words, programmers do not have direct access to these views to call methods on them. You have access to these views only through the RemoteViews (like a gatekeeper).

We will cover the relevant methods of a RemoteViews class when we explore the example in the next main section. For now, remember that only a limited set of views in Listing 21-3 are allowed in the widget layout file.

Understanding configure Attribute

The widget definition (Listing 21-2) uses the configure attribute to specify the configuration activity that needs to be invoked when the user creates a widget instance. This configuration activity specified in Listing 21-2 is the ConfigureBDayWidgetActivity. This activity (Figure 21-3) is like any other Android activity. Form fields on this activity are used to collect the information needed by a widget instance.

Understanding Widget Instance Creation Phase

When a user chooses a widget to create a widget instance, Android invokes the configuration activity (Figure 21-3) if it is defined in the configuration XML file for the widget. If this configuration activity is not defined then this phase skipped and the widget is presented directly on the home page. When invoked this configuration activity does the following:

  1. Receive the widget instance ID from the invoking intent that started the configuration activity.
  2. Prompt the user through form fields to collect the widget-instance–specific information.
  3. Persist the widget instance information so that subsequent calls to AppWidgetProvider’s onUpdate method have access to this information.
  4. Prepare to display the widget view for the first time by retrieving the widget view layout and create a RemoteViews object with it.
  5. Call methods on the RemoteViews object to set values on individual view objects, such as text and images.
  6. Also use the RemoteViews object to register any onClick events on any of the subviews of the widget.
  7. Tell the AppWidgetManager to paint the RemoteViews on the home screen using the instance ID of that widget.
  8. Return the widget ID, and close.

Notice that the first population of the widget in this case is done by the configuration activity and not AppWidgetProvider’s onUpdate() method.

Note  The configuration activity is optional. If the configuration activity is not specified, the call goes directly to the onUpdate() method of the AppWidgetProvider. It is up to onUpdate() to update the view.

Android will undertake this process for each widget instance that the user creates. Besides invoking the configuration activity, Android also invokes the onEnabled callback of the AppWidgetProvider. Let’s briefly consider the callbacks on an AppWidgetProvider class by taking a look at the shell of our BDayWidgetProvider (see Listing 21-4). We will examine the complete listing of this file later in Listing 21-10.

Listing 21-4. A Widget Provider Shell

// filename: BDayWidgetProvider.java(ProAndroid5_ch21_TestWidgets.zip)
public class BDayWidgetProvider extends AppWidgetProvider {
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
                        int[] appWidgetIds){}
    public void onDeleted(Context context, int[] appWidgetIds){}
    public void onEnabled(Context context){}
    public void onDisabled(Context context) {}
}

The onEnabled() callback method indicates that there is at least one instance of the widget up and running on the home screen. This means a user must have dropped the widget on the home page at least once. In this call, you will need to enable receiving messages for this broadcast receiver component (you will see this in Listing 21-10). The SDK base class AppWidgetProvider has the functionality to enable or disable receiving broadcast messages.

The onDeleted() callback method is called when a user drags the widget instance view to the trash can. This is where you will need to delete any persistent values you are holding for that widget instance.

The onDisabled() callback method is called after the last widget instance is removed from the home screen. This happens when a user drags the last instance of a widget to the trash. You should use this method to unregister your interest in receiving any broadcast messages intended for this component (you will see this in Listing 21-9).

The onUpdate() callback method is called every time the timer specified in Listing 21-2 expires. This method is also called the very first time the widget instance is created if there is no configuration activity. If there is a configuration activity, this method is not called at the creation of a widget instance. This method will subsequently be called when the timer expires at the frequency indicated.

Understanding onUpdate Phase

Once the widget instance is on the home screen, the next significant event is the expiration of the timer. Android will call onUpdate() in response to that timer. Because onUpdate() is called is through a broadcast receiver, the corresponding Java process will be loaded and will remain live until the end of that call. Once the call returns, the process will be ready to be taken down.

Once you have the necessary data available to update the widget in the onUpdate() method, you can invoke the AppWidgetManager to paint the remote view. This goes to show that the AppWidgetProvider class is stateless and may even be incapable of maintaining static variables between invocations. This is because the Java process containing this broadcast receiver class could be taken down and reconstructed between two invocations, resulting in re-initialization of static variables.

As a result, you will need to come up with a scheme to remember state if that is required. You can save the state of the widget instance in a persistent store such as a file, shared preferences, or a SQLite database. In the examples in this chapter, we used shared preferences as the persistence API.

Caution  To save power, Google recommends that the duration of the updates be more than an hour, so the device won’t wake up too often. Starting with the 2.0 API, there is a restriction of 30 minutes or more for the update timeout.

For durations that are shorter, such as only seconds, you need to call this onUpdate() method yourself by using the facilities in the AlarmManager class. When you use AlarmManager, you also have the option not to call onUpdate() but, instead, do the work of onUpdate() in alarm callbacks. Refer to Chapter 17 for working with the alarm manager.

This is what you typically need to do in an onUpdate() method:

  1. Make sure the configurator has finished its work; otherwise, just return. This should not be problem in releases 2.0 and above, where the duration is expected to be longer. Otherwise, based on the update interval (when it is too small) it is possible that onUpdate() will be called before the user has finished configuring the widget in the configurator.
  2. Retrieve the persisted data for that widget instance.
  3. Retrieve the widget view layout, and create a RemoteViews object with it.
  4. Call methods on the RemoteViews to set values on individual view objects such as text and images.
  5. Register any onClick events on any of the views by using pending intents.
  6. Tell the AppWidgetManager to paint the updated RemoteViews using the instance ID.

As you can see, there is a lot of overlap between what a configurator does initially and what the onUpdate() method does. You may want to reuse this functionality between the two places.

Understanding Widget View Mouse Click Event Callbacks

As stated, the onUpdate() method keeps the widget views up to date. The widget view and subelements in that view could have callbacks registered for a mouse click. Typically, the onUpdate() method uses a pending intent to register an action for an event like a mouse click. This action could then start a service or start an activity such as opening up a browser.

This invoked service or activity can then communicate back with the view, if needed, using the widget instance ID and the AppWidgetManager. Hence, it is important that the pending intent carries with it the widget instance ID.

Deleting a Widget Instance

Another distinct event that can happen to a widget instance is that it can get deleted. To do this, a user has to long-press the widget on the home screen. This will enable the trash can to show on the home screen. The user can then drag the widget instance to the trash can to delete the widget instance from the screen.

Doing so calls the onDelete() method of the widget provider. If you have saved any state information for this widget instance, you will need to delete that data in this onDelete method.

Android also calls onDisable() if the widget instance that has just been deleted is the last of the widget instances of this type. You will use this callback to clean up any persistence attributes that are stored for all widget instances and also unregister for callbacks from the widget onUpdate() broadcasts.

Uninstalling Widget Packages

There is a need to clean up the widgets if you are planning to uninstall and install a new release of your .apk file containing these widgets.

It is recommended that you remove or delete all widget instances before trying to uninstall the package. Follow the directions in the “Deleting a Widget Instance” section to delete each widget instance until none remain.

Then, you can uninstall and install the new release. This is especially important if you are using the Eclipse ADT to develop your widgets, because during the development time, ADT tries to do this every time you run the application. So, between runs, make sure you remove the widget instances.

Implementing A Sample Widget Application

So far, we have covered the theory and approach behind widgets. Let’s create the sample widget whose behavior has been used as the example to explain widget architecture. We will develop, test, and deploy this Birthday Widget.

Each Birthday Widget instance will show a name, the date of the next birthday, and how many days from today until the birthday. It will also create an onClick area where you can click to buy gifts. This click will open a browser and take you to www.google.com.

The layout of the finished widget should look like Figure 21-4.

9781430246800_Fig21-04.jpg

Figure 21-4. Birthday Widget look and feel

The implementation of this widget consists of the following widget-related files. The entire project is also available for download at the URL mentioned in the “References” section of this chapter.

The basic files are

  • AndroidManifest.xml: Where the AppWidgetProvider is defined (see Listing 21-5)
  • res/xml/bday_appwidget_provider.xml: Widget dimensions and layout (see Listing 21-2)
  • res/layout/bday_widget.xml: The widget layout (see Listing 21-6)
  • res/drawable/box1.xml: Provides boxes for sections of the widget layout (see Listing 21-7)
  • src/.../BdayWidgetProvider.java: Implementation of the AppWidgetProvider class (see Listing 21-10)

These files implement the widget configuration activity:

  • src/.../ConfigureBDayWidgetActivity.java: Configuration activity (see Listing 21-8)
  • layout/edit_bday_widget.xml: Layout for taking the name and birthday (see Listing 21-9)

These files store/retrieve the state of a widget instance using preferences:

  • src/.../IWidgetModelSaveContract.java: Contract for saving and retrieving a widget’s data (See in downloadable project)
  • src/.../APrefWidgetModel.java: Abstract preference-based widget model that saves widget data in preferences (see in downloadable project)
  • src/.../BDayWidgetModel.java: Widget model holding the data for a widget view (see in downloadable project)
  • src/.../Utils.java: A few utility classes (see in downloadable project)

We will walk through some of the key files and explain any additional concepts that bear further consideration. You can get the rest of the files from the downloadable project for this chapter.

Defining the Widget Provider

For the Birthday Widget project the manifest file is in Listing 21-5. It has the declarations for the widget provider BDayAppWidgetProvider as a broadcast receiver and also the definition for the configuration activity ConfigureBDayWidgetActivity. Notice how the widget provider definition also points to the widget definition XML file @xml/bday_appwidget_provider.

Listing 21-5. Android Manifest File for BDayWidget Sample Application

<?xml version="1.0" encoding="utf-8"?>
<!-- file: AndroidManifest.xml(ProAndroid5_ch21_TestWidgets.zip) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.androidbook.BDayWidget"
      android:versionCode="1"
      android:versionName="1.0.0">
<application android:icon="@drawable/icon"
             android:label="Birthday Widget">
<!--
**********************************************************************
*  Birthday Widget Provider Receiver
**********************************************************************
 -->
   <receiver android:name=".BDayWidgetProvider">
      <meta-data android:name="android.appwidget.provider"
             android:resource="@xml/bday_appwidget_provider"/>
      <intent-filter>
           <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
      </intent-filter>
   </receiver>
<!--
**********************************************************************
*  Birthday Provider Configuration activity
**********************************************************************
 -->
   <activity android:name=".ConfigureBDayWidgetActivity"
             android:label="Configure Birthday Widget">
       <intent-filter>
           <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
       </intent-filter>
   </activity>

    </application>
    <uses-sdk android:minSdkVersion="3"/>
</manifest>

The application label identified by "Birthday Widget" in the following line

<application android:icon="@drawable/icon" android:label="Birthday Widget">

is what shows up in the widget pick list (see Figure 21-2) of the home page. You can also indicate in the widget definition XML file (Listing 21-2) an alternate icon to be shown when the widget is listed (also called a preview). The configuration activity definition is like any other normal activity, except that it needs to declare itself as capable of responding to android.appwidget.action.APPWIDGET_CONFIGURE actions.

Refer to the widget definition file @xml/bday_appwidget_provider in Listing 21-2 to see how the widget size and a path to the layout file are specified. This layout file is just like any other layout file for a view in Android. Listing 21-6 shows the layout file we used to produce the widget layout shown in Figure 21-4.

Listing 21-6. Widget View Layout Definition for BDayWidget

<?xml version="1.0" encoding="utf-8"?>
<!-- res/layout/bday_widget.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"  android:layout_height="fill_parent"
    android:background="@drawable/box1">
<TextView
    android:id="@+id/bdw_w_name"
    android:layout_width="fill_parent" android:layout_height="40sp"
    android:text="Anonymous"  android:background="@drawable/box1"
    android:gravity="center"  android:layout_weight="0"/>
<LinearLayout
    android:orientation="horizontal"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:layout_weight="1">
       <TextView
           android:id="@+id/bdw_w_days"
           android:layout_width="wrap_content" android:layout_height="fill_parent"
           android:gravity="center" android:layout_weight="50"
                   android:text="0" android:textSize="30sp" />
       <TextView
           android:id="@+id/bdw_w_button_buy"
           android:layout_width="wrap_content"  android:layout_height="fill_parent"
           android:layout_weight="50"  android:gravity="center"
           android:textSize="20sp"  android:text="Buy"
           android:background="#FF6633"/>
</LinearLayout>
<TextView
    android:id="@+id/bdw_w_date"
    android:layout_width="fill_parent"  android:layout_height="40sp"
    android:gravity="center" android:layout_weight="0"
    android:text="1/1/2000" android:background="@drawable/box1"/>
</LinearLayout>

Some of the controls also use a shape definition file called box1.xml to define the borders. The code for the shape definition file is shown in Listing 21-7.

Listing 21-7. A Boundary Box Shape Definition

<!-- res/drawable/box1.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke android:width="4dp" android:color="#888888"/>
    <padding android:left="2dp" android:top="2dp"
            android:right="2dp" android:bottom="2dp"/>
    <corners android:radius="4dp"/>
</shape>

Implementing Widget Configuration Activity

For the Birthday Widget example, the configuration of the widget responsibilities are implemented in ConfigureBDayWidgetActivity. Source code for this class is in Listing 21-8.

Listing 21-8. Implementing a Configuration Activity

// file: ConfigureBDayWidgetActivity.java(ProAndroid5_ch21_TestWidgets.zip)
public class ConfigureBDayWidgetActivity extends Activity
{
   private static String tag = "ConfigureBDayWidgetActivity";
   private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.edit_bday_widget);
        setupButton(); //setup the save button

        //Get the widget instanceid from the intent extra
        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            mAppWidgetId = extras.getInt(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }
    }
    private void setupButton(){
       Button b = (Button)this.findViewById(R.id.bdw_button_update_bday_widget);
       b.setOnClickListener(
             new Button.OnClickListener(){
                public void onClick(View v)  {
                   saveConfiguration(v);
                }
             });
    }
    //Read name and date.
    //Call updateAppWidgetLocal to save the values for this instance
    //in that method also send the view to the homepage.
    //Return the result of the configuration activity to the SDK
    //finish the activity.
    private void saveConfiguration(View v){
       String name = this.getName();
       String date = this.getDate();
       if (Utils.validateDate(date) == false){
          this.setDate("wrong date:" + date);
          return;
       }
       if (this.mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID){
          return;
       }
       updateAppWidgetLocal(name,date);
       Intent resultValue = new Intent();
       resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
       setResult(RESULT_OK, resultValue);
       finish();
    }
    private String getName(){
        EditText nameEdit =
            (EditText)this.findViewById(R.id.bdw_bday_name_id);
        String name = nameEdit.getText().toString();
        return name;
    }
    private String getDate(){
        EditText dateEdit = (EditText)this.findViewById(R.id.bdw_bday_date_id);
        String dateString = dateEdit.getText().toString();
        return dateString;
    }
    private void setDate(String errorDate){
        EditText dateEdit = (EditText)this.findViewById(R.id.bdw_bday_date_id);
        dateEdit.setText("error");
        dateEdit.requestFocus();
    }
    private void updateAppWidgetLocal(String name, String dob){
       //Create an object to hold the data: widgetid, name, and dob
       BDayWidgetModel m = new BDayWidgetModel(mAppWidgetId,name,dob);
       //Create the view and send it to the home screen
       updateAppWidget(this,AppWidgetManager.getInstance(this),m);
       //Use the data model object to save the id, name, and dob in prefs
       m.savePreferences(this);
    }
    //A key method where a lot of magic happens
    public static void updateAppWidget(Context context,
            AppWidgetManager appWidgetManager,
            BDayWidgetModel widgetModel)
    {
      //Construct a RemoteViews Object from the widget layout file
      RemoteViews views = new RemoteViews(context.getPackageName(),
                    R.layout.bday_widget);

      //Use the control ids in the layout to set values on them.
      //Notice that these methods are limited and available on the
      //on the RemoteViews object. In other words we are not using the
      //TextView directly to set these values.
      views.setTextViewText(R.id.bdw_w_name
         , widgetModel.getName() + ":" + widgetModel.iid);

      views.setTextViewText(R.id.bdw_w_date
            , widgetModel.getBday());

      //update the name
      views.setTextViewText(R.id.bdw_w_days,
                           Long.toString(widgetModel.howManyDays()));

      //Set intents to invoke other activities when widget is clicked on
      Intent defineIntent = new Intent(Intent.ACTION_VIEW,
              Uri.parse("http://www.google.com"));
      PendingIntent pendingIntent =
           PendingIntent.getActivity(context,
                    0 /* no requestCode */,
                    defineIntent,
                    0 /* no flags */);
       views.setOnClickPendingIntent(R.id.bdw_w_button_buy, pendingIntent);

      // Tell the widget manager to paint the remote view
      appWidgetManager.updateAppWidget(widgetModel.iid, views);
   }
}

Before we cover what this code does, the layout used by this widget configuration activity is in Listing 21-9. This layout is straightforward. You can also see this visually in Figure 21-3.

Listing 21-9. Layout Definition for Configuration Activity

<?xml version="1.0" encoding="utf-8"?>
<!-- res/layout/edit_bday_widget.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/root_layout_id"  android:orientation="vertical"
   android:layout_width="fill_parent"  android:layout_height="fill_parent">
<TextView
    android:id="@+id/bdw_text1"  android:layout_width="fill_parent"
    android:layout_height="wrap_content" android:text="Name:" />
<EditText
    android:id="@+id/bdw_bday_name_id" android:layout_width="fill_parent"
    android:layout_height="wrap_content" android:text="Anonymous" />
<TextView
    android:id="@+id/bdw_text2" android:layout_width="fill_parent"
    android:layout_height="wrap_content" android:text="Birthday (9/1/2001):" />
<EditText
    android:id="@+id/bdw_bday_date_id" android:layout_width="fill_parent"
    android:layout_height="wrap_content"  android:text="ex: 10/1/2009" />
<Button
    android:id="@+id/bdw_button_update_bday_widget" android:layout_width="fill_parent"
    android:layout_height="wrap_content" android:text="update"/>
</LinearLayout>

Going back to the configuration activity code in Listing 21-8, it accomplishes the following tasks:

  • Reading the widget instance ID from invoking intent
  • Collecting the name and date of birth using form fields
  • Obtaining RemoteViews by loading widget layout file
  • Setting text values on the RemoteViews
  • Registering a pending intent through RemoteViews
  • Invoking the AppWidgetManager to send the RemoteViews to the widget
  • Saving the name and date of birth in preferences against this widget instance ID. This is done through the class BDayWidgetModel. We will talk about this shortly.
  • Returning at the end with a result.

Note  The static function udpateAppWidget can be called from anywhere as long as you know the widget ID. This suggests that you can update a widget from anywhere on your device and from any process, both visual and nonvisual.

Notice how we are passing the widget ID back to the invoker of this configuration activity. This is how AppWidgetManager knows that the configuration activity is completed for that widget instance.

Let’s talk about saving and retrieval of the widget instance state through BDayWidgetModel object in Listing 21-8. The role of BDayWidgetModel object is to store and retrieve three values: The widget instance ID (primary key), name, and date of birth. This class uses the preferences API to persist and read back these values. Alternatively, you can use any persistence mechanism for this need. We are not including the source code for this class as it is quite a simple need to implement. In the downloadable project for this chapter we have an implementation for this class that is a bit more extensive, where we coded a reusable framework to store values for any java object in the preferences. We have amply documented the source code so that you can use it as is for other needs or tweak it further and use reflection to simplify further. In the end you will have a model framework that is quite extensible. As this is not the primary goal of this chapter we have not gotten into those details here. What matters for this chapter is that these three values, the instance ID, name, and dob be saved and retrieved. You can follow the names on the BDayWidgetModel as a guide.

Implementing a Widget Provider

Let’s see now how we will respond to the life cycle events of widgets by examining the widget provider class. Listing 21-10 implements the widget provider class.

Listing 21-10. Source code for Sample Widget Provider: BDayWidgetProvider

// file: BDayWidgetProvider.java(ProAndroid5_ch21_TestWidgets.zip)
public class BDayWidgetProvider extends AppWidgetProvider  {
    private static final String tag = "BDayWidgetProvider";
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
                        int[] appWidgetIds)  {
        final int N = appWidgetIds.length;
        for (int i=0; i<N; i++)  {
            int appWidgetId = appWidgetIds[i];
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }
    public void onDeleted(Context context, int[] appWidgetIds) {
        final int N = appWidgetIds.length;
        for (int i=0; i<N; i++) {
               BDayWidgetModel bwm = BDayWidgetModel.retrieveModel(context, appWidgetIds[i]);
               bwm.removePrefs(context);
        }
    }
    public void onEnabled(Context context) {
        BDayWidgetModel.clearAllPreferences(context);
        PackageManager pm = context.getPackageManager();
        pm.setComponentEnabledSetting(
                new ComponentName("com.androidbook.BDayWidget",
                       ".BDayWidgetProvider"),
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);
    }

    public void onDisabled(Context context) {
        BDayWidgetModel.clearAllPreferences(context);
        PackageManager pm = context.getPackageManager();
        pm.setComponentEnabledSetting(
                new ComponentName("com.androidbook.BDayWidget",
                       ".BDayWidgetProvider"),
                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                PackageManager.DONT_KILL_APP);
    }
    private void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                          int appWidgetId) {
       BDayWidgetModel bwm = BDayWidgetModel.retrieveModel(context, appWidgetId);
       if (bwm == null) {return;}
       ConfigureBDayWidgetActivity.updateAppWidget(context, appWidgetManager, bwm);
   }
}

In the “Life Cycle of a Widget” section we discussed the responsibilities of these methods. For the Birthday Widget, all these methods make use of the BDayWidgetModel to retrieve the data associated with a widget instance for which the callbacks are called. Some of these methods on the BDayWidgetModel are removePrefs(), retrievePrefs(), and clearAllPreferences().

The update callback method is called for all the widget instances of this widget type. This method must update all the widget instances. The widget instances are passed in as an array of IDs. For each id, the onUpdate() method will locate the corresponding widget instance model and call the same method that is used by the configuration activity (see Listing 21-8) to display the retrieved widget model.

In the onDeleted() method, we have instantiated a BDayWidgetModel and then asked it to remove itself from the preferences persistence store.

In the onEnabled() method, because it is called only once when the first instance comes into play, we have cleared all persistence of the widget models so that we start with a clean slate. We do the same in the onDisabled() method so that no memory of widget instances exists.

In the onEnabled() method, we enable the widget provider component so that it can receive broadcast messages. In the onDisabled() method, we disable the component so that it won’t look for any broadcast messages.

Collection-Based Widgets

Starting with SDK 3.0, Android has expanded the widgets to include widgets based on collections. We don’t have room in the print copy of this book. We will include the chapter from the previous edition on collection widgets at our online site for download.

Resources

Here are helpful references to the topics that are covered in this chapter:

Summary

Widgets are often used alongside your applications in Android. This chapter has covered the essentials you need to create and configure widgets. A supplemental chapter on list widgets is provided online.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.143.40