CHAPTER FOURTEEN: Making an Android Widget

Chapter opener image: © Fon_nongkran/Shutterstock

Introduction

App widgets, also known as widgets, are small apps that are embedded in other apps, called app widget hosts. The Home screen and the Lock screen are examples of app widget hosts. We can also implement our own app widget host, but that is beyond the scope of this chapter. Widgets can receive periodic updates and update their view accordingly. The minimum frequency of automatic periodic updates is 30 minutes. Many Android devices come with native widgets already installed. A well-known and common widget is a weather app widget from www.accuweather.com. In this chapter, we create a widget that displays the current temperature at a location specified by the user during the installation of the widget.

14.1 Steps in Making a Widget, Temperature Widget, Version 0

In order to create a widget, we first start by creating a standard Android application project using the Empty Activity template. Although we could reuse (and modify) the activity_main.xml file, we choose to create a new layout file in the layout directory for the widget. We name it widget_layout.xml.

The android.appwidget package contains classes that enable us to create and manage widgets. TABLE 14.1 lists some of these classes.

In order to create a widget, we do the following:

  • ▸ Add a receiver element inside the application element in the AndroidManifest.xml file.

  • ▸ Define an AppWidgetProviderInfo object in an eXtensible Markup Language (XML) resource located in the res/xml directory.

  • ▸ Define an app widget layout in an XML file in the res/layout directory.

  • ▸ Extend the AppWidgetProvider class or the BroadcastReceiver class.

In Version 0, we create a very simple widget that displays a hard coded String.

TABLE 14.1 Selected classes of the android.appwidget package

Class Description
AppWidgerProvider A convenience class we can extend to implement a widget provider. This class contains life-cycle methods for a widget that we can override.
AppWidgetProviderInfo Describes the metadata in a widget provider, such as icon, minimum width and height, update frequency, etc.
AppWidgetManager Manages the widget provider and the widgets.

First, we update the AndroidManifest.xml file. We include a receiver element inside the application element in the AndroidManifest.xml file using the following pattern:

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

In this example, TemperatureProvider is the name of our class that extends AppWidgetProvider, and widget_info.xml is the name of the file inside the xml directory of the res directory that contains the widget information such as size and update frequency.

EXAMPLE 14.1 shows the AndroidManifest.xml file. At lines 12–22, the receiver element replaces the default activity element. The android:name attribute inside the receiver element at line 12 is mandatory. Its value is the name of the class that extends AppWidgetProvider. The action element (lines 14–15) inside the intent-filter element is mandatory and must have an android:name attribute whose value is the equivalent of the ACTION_APPWIDGET_UPDATE constant of the AppWidgetManager class. That means that this widget receives an update broadcast at the frequency specified in the widget info xml file. The meta-data element, at lines 18–20, specifies the AppWidgetProviderInfo information via its android:name and android:resource attributes, both mandatory:

EXAMPLE 14.1 The AndroidManifest.xml file

  • ▸ The android:name attribute specifies the metadata name: the value android.appwidget.provider is used to identify the metadata as the AppWidgetProviderInfo type (line 19).

  • ▸ The android:resource attribute specifies the resource location and name of the xml file that contains the AppWidgetProviderInfo information (line 20).

EXAMPLE 14.2 shows the widget_info.xml file. It defines an AppWidgetProviderInfo object in an XML resource. TABLE 14.2 shows some public instance variables and corresponding XML attributes of the AppWidgetProviderInfo class.

EXAMPLE 14.2 The widget_info.xml file

The res directory does not contain an xml directory by default, so we need to create it. Since we specify widget_info as the name of the xml file defining the AppWidgetProviderInfo object in the AndroidManifest.xml file in Example 14.1, we add an xml file in the xml directory named widget_info.xml. That file must contain a single appwidget-provider element. It is defined at lines 2–8 of Example 14.2. At line 4, we specify widget_layout.xml, located in the res/layout directory, as the layout resource of the widget. At lines 5–6, we set its minimum height and width to 50 and 200 pixels. These will be the width and height of the widget when it is created and added to the host. An Android home screen is organized as a grid of cells in which widgets (and icons) can be placed. The grid can vary by device.

It could be 4 × 4 for a phone or 8 × 7 for a tablet, for example. The number of horizontal or vertical cells that the widget requires is the minimum number of cells required to accommodate minWidth or minHeight pixels, respectively. TABLE 14.3 shows Google’s recommendations. For example, if we have a widget whose minWidth value is 200 and whose minHeight value is 70, its graphical representation is allocated a 2 × 4 rectangle of cells: the 200 density pixels require 4 vertical cells and the 70 density pixels require 2 horizontal cells.

TABLE 14.2 Selected public instance variables and XML attributes of the AppWidgetProviderInfo class

Public Instance Variable XML Attribute Description
int minWidth android:minWidth The width of the widget when added to the host, in dp.
int minHeight android:minHeight The height of the widget when added to the host, in dp.
int minResizeWidth android:minResizeWidth The minimum width the widget can be resized to, in dp.
int minResizeHeight android:minResizeHeight The minimum height the widget can be resized to, in dp.
int updatePeriodMillis android:updatePeriodMillis The frequency of updates for the widget, in ms. Minimum frequency is 30 minutes.
int widgetCategory android:widgetCategory Specifies whether this widget can be displayed on the Home screen, Lock screen ( Keyguard ), or both.

TABLE 14.3 Number of cells versus widget size per dimension

# of Cells per Dimension Widget Maximum Dimension in Density Pixels (dp)
n 70 * n – 30
1 40
2 110
3 180
4 250

At line 7, we set the update frequency to be 1800000 milliseconds, which is equal to 30 minutes. This is the minimum frequency at which a widget can be automatically updated. However, we can trigger a widget update by code by setting up some event handling on the widget, for example, if the user touches the widget. We do that in Version 2.

Example 14.2 specifies at line 4 that the layout of the widget is defined in the widget_layout.xml file. Although a widget is typically small with a simple GUI, a widget can have a complex GUI, but with some restrictions. TABLE 14.4 lists the Views and layout managers that a widget can use.

TABLE 14-4 Views and layout managers that we can use in a widget

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

EXAMPLE 14.3 shows the widget_layout.xml file. It is organized in a LinearLayout (line 2) and contains only a TextView (lines 7–12), which in this version shows some hard coded text using the string resource city_and_temp (line 11); city_and_temp is defined in strings.xml as follows:

<string name="city_and_temp">New York, NY
75u00B0F</string>

It uses the Unicode character u00B0 to encode the degree sign (°). Inside strings.xml, we change the value of the app_name String from TemperatureWidgetV0 to TemperatureV0.

EXAMPLE 14.3 The widget_layout.xml file

We now create a class that extends AppWidgetProvider. As specified in the AndroidManifest.xml file in Example 14.1, we name it TemperatureProvider. TABLE 14.5 shows some life-cycle methods of the AppWidgetProvider class. The constants in column 2 are from the AppWidgetManager class. At the minimum, we need to override the onUpdate method so that we display the widget. The onUpdate method provides three parameters:

  • ▸ A Context reference to the context in which this AppWidgetProvider is running.

  • ▸ An AppWidgetManager: it enables us to update one or more widgets of the current type.

  • ▸ An array of widget ids: the ids of the widgets of that provider for which an update is needed.

TABLE 14-5 Selected methods of the AppWidgetProvider class

Method Type of BROADCAST (or Action Value of Intent) Description
void onUpdate( Context context, AppWidgetManager manager, int [ ] appWidgetIds ) ACTION_APPWIDGET_UPDATE Called when this widget first runs and when it is updated ( at the frequency defined in updatePeriodMillis ).
void onAppWidgetOptionsChanged( Context context, AppWidgetManager manager, int appWidgetId, Bundle newOptions ) ACTION_APPWIDGET_OPTIONS_CHANGED Called when this widget has been resized.
void onEnabled( ) ACTION_APPWIDGET_ENABLED Called when the first widget of that type is added to the host screen.
void onDeleted( ) ACTION_APPWIDGET_DELETED Called when a widget of that type is deleted from the host screen.
void onDisabled( ) ACTION_APPWIDGET_DISABLED Called when the last widget of that type is deleted from the host screen.
void onReceive( Context context, Intent intent ) Automatically called before the onUpdate method before a widget is configured and after the widget’s configuration activity has finished. Overrides this method to dispatch calls to the other methods of this class.

It is possible that several widgets of that class are installed. This is why we refer to an array of widget ids rather than a single widget id. For example, if this widget is customizable based on some location, we could have a widget of that class displaying the temperature in New York, NY, and another one displaying the temperature in Palo Alto, CA.

Inside the onUpdate method, we loop through all the widget ids of type TemperatureProvider and update them using the AppWidgetManager parameter.

Note that we could choose to only update the widgets that need an update by using the appWidgetIds parameter of the onUpdate method.

TABLE 14.6 shows some methods of the AppWidgetManager class.

The RemoteViews class, from the android.widget package, describes a hierarchy of Views that can be displayed. It can be inflated from an XML resource. It also includes methods that can manage the contents of the hierarchy of Views. TABLE 14.7 shows a constructor of the RemoteViews class.

TABLE 14.6 Selected methods of the AppWidgetManager class

Method Description
int [ ] getAppWidgetIds( ComponentName provider ) Returns the list of widget ids for the widgets of the type of this AppWidgetProvider provider
void updateAppWidget( int appWidgetId, RemoteViews views ) Sets views as the RemoteViews for the widget whose id is appWidgetId.

TABLE 14.7 Selected Methods of the RemoteViews class

Constructor Description
RemoteViews( String packageName, int layoutResourceId ) Creates and returns a RemoteViews from the resource identified by layoutResourceId in the package named packageName.

EXAMPLE 14.4 shows the TemperatureProvider class. The onUpdate method is at lines 9–17. At lines 13–14, we instantiate a RemoteViews object for the layout defined in the widget_layout.xml file. The appWidgetIds parameter stores the widget ids of the widgets that need to be updated. At lines 15–16, we loop through all of them with the appWidgetManager parameter.

EXAMPLE 14.4 The TemperatureProvider class

FIGURE 14.1 The app icons, including the Widget Preview icon, inside the emulator

FIGURE 14.2 The list of the widgets in the emulator

If there are several widgets of the same provider class installed on a device, they have most likely been installed at different times. In Example 14.4, we only update those that need to be updated. We can also update all of them regardless of which ones need to be updated, so that they are synchronized. We do that later in the chapter.

We can test a widget inside the emulator. If we click on the apps icon, then click on the Widget Preview icon (see FIGURE 14.1), and then click on the widget (see FIGURE 14.2), we can see a preview of the widget (see FIGURE 14.3).

FIGURE 14.3 A preview of the TemperatureWidgetV0 widget inside the emulator

FIGURE 14.4 The tablet after a long press showing a menu

After we run Example 14.4 on a device like a tablet, we need to install the widget on the tablet. In order to do that, we do the following:

  • ▸ Long press on the screen in order to bring the menu shown in FIGURE 14.4 (we can also click on the Apps icon),

  • ▸ Choose Apps and widgets and click on the Widgets tab at the top of the screen. Depending on how many widgets are already installed on the device, we may have to swipe the screen in order to get to the screen that shows the widget, which is shown in FIGURE 14.5.

  • ▸ In order to install it, we press on it and move it to the location we want. When we release it, the widget is running.

FIGURE 14.6 shows the widget running on the Home screen. Note that we can repeat that operation and install a second widget of the same provider class on the Home screen.

FIGURE 14.5 The tablet showing the TemperatureWidget, Version 0, widget

FIGURE 14.6 The TemperatureWidgetV0 widget running inside the home screen of the tablet

14.2 Styling the Widget, Temperature Widget, Version 1

In Version 1, we style the widget so that it looks more like the widgets we are accustomed to, but we do not improve its functionality yet. We style it in two ways:

  • ▸ We give it a background.

  • ▸ We style the TextView that displays the text.

To define a background, we create an XML file in the drawable directory and define a rectangular shape (line 3) as shown in EXAMPLE 14.5. At line 5, we specify that the corners of the rectangle be rounded with a radius of 10 for each corner. At lines 7–9, we specify the stroke of the rectangle: its width is 2 pixels (line 8) and its color is sea foam green (line 9). At lines 11–14, we specify a gradient to fill the rectangle with. Its starting color is ivory (line 12) and its ending color is teal (line 13) with two different opacity levels. It is a linear gradient going from left to right at a 45 degree angle (line 14).

EXAMPLE 14.5 The widget_shape.xml file

Google recommends to leave a margin between the edges of the widget’s bounding rectangle and the widget’s frame, and some padding inside the widget’s frame, as shown in FIGURE 14.7. The red outside rectangle is the bounding rectangle, or bounding box, of the widget. The blue rectangle is the frame of the widget. The widget contents, in particular Views, are inside the black rectangle.

FIGURE 14.7 Margin and padding of a widget

EXAMPLE 14.6 shows the updated widget_layout.xml file. The widget_layout.xml file defines the layout for the widget using a LinearLayout element. At line 7, we assign the value @drawable/widget_shape to the android:background XML attribute to specify that the background of the LinearLayout element is defined by the widget_shape.xml file. We set the margin to 5 pixels at line 6. At line 14, we center the text inside the TextView element both horizontally and vertically. We set the padding to 5 pixels at line 15. Since we need to access the TextView by code in Versions 2 and after, we give an id at line 10.

FIGURE 14.8 shows our widget, Version 1, running inside the tablet alongside a widget from Version 0.

EXAMPLE 14.6 The widget_layout.xml file

FIGURE 14.8 The Version 0 and Version 1 widgets inside the Home screen of the tablet

14.3 Updating the Data in the Widget, Temperature Widget, Version 2

In Version 2, we make the widget dynamic: we retrieve the date and time dynamically from the device, and display it inside the widget. In order to implement this feature, we do the following:

  • ▸ Retrieve the date and time by code and build a String that includes it.

  • ▸ Use that String to update the TextView inside the widget.

The Date class of the java.util package enables us to retrieve the data and time dynamically. The DateFormat class of the java.text package enables us to format that date and time into a String.

The RemoteViews class contains methods that enable us to manage Views inside the widget like we manage Views inside an app. TABLE 14.8 shows some methods of the RemoteViews class as well as their equivalent methods and classes for a regular app. The methods of RemoteViews include an additional parameter to identify the View that is the subject of the method call. That parameter is typically the id of the View.

For example, if the layout XML file for our widget is widget_layout.xml and it contains a TextView whose id is display, we could set the text of that TextView to Hello Widget, its color to green, and its size to 32 as follows:

RemoteViews remoteViews = new RemoteViews( context.getPackageName( ),
                                           R.layout.widget_layout );
remoteViews.setTextViewText( R.id.display, "Hello Widget");
remoteViews.setTextColor( R.id.display, Color.parseColor("#00FF00"));
remoteViews.setTextViewTextSize( R.id.display, TypedValue.COMPLEX_UNIT_SP, 32.0f );

TABLE 14.8 Selected methods of the RemoteViews class and their equivalent methods (and their classes)

Method of RemoteViews Equivalent Method From Class
void addView( int viewId, RemoteViews nestedView ) void addView( View child ) ViewGroup
void setTextViewText( int viewId, CharSequence text ) void setText ( CharSequence text ) TextView
void setTextViewTextSize( int viewId, int units, float size ) void setTextSize( int units, float size ) TextView
void setTextColor( int viewId, int color ) void setTextColor( int color ) TextView

EXAMPLE 14.7 The TemperatureProvider class, app Version 2

EXAMPLE 14.7 shows the updated TemperatureProvider class. At lines 9–10, we import the DateFormat and Date classes. At line 17, we get a reference to today’s date, dateToday. We format it to a simpler String, today, at line 18, eliminating the day of the week and the time zone. At line 19, we get a Resources reference so we can convert the city_and_temp String resource into a String (line 21). Resources is imported at line 6. At lines 20–21, we concatenate today and our default city, state, and temperature into the String displayString. At line 25, we set the text inside the TextView whose id is display to displayString.

FIGURE 14.9 shows our widget, Version 2, running inside the tablet alongside widgets from Versions 0 and 1.

FIGURE 14.9 The Versions 0, 1, and 2 widgets running inside the Home screen of the tablet

14.4 Updating the Data in the Widget by Clicking on It, Temperature Widget, Version 3

A widget can update itself automatically at a specified frequency. The minimum frequency is 30 minutes. Often, the user does not want to wait 30 minutes but wants to force an immediate update. In Version 3, we implement that functionality. We set up event handling so that, by clicking on the widget, the widget calls the onUpdate method and updates itself.

The setOnClickPendingIntent method of RemoteViews, shown in TABLE 14.9, enables us to specify an intent that has been created as a pending intent that will be launched when the user clicks on a View within the RemoteViews hierarchy identified by its id.

The PendingIntent class encapsulates the concept of a pending intent. A pending intent comprises an intent and an action to perform when the intent is launched. A PendingIntent can be created using the getBroadcast static method of the PendingIntent class, shown in TABLE 14.10, in conjunction with some of its constants, shown in TABLE 14.11.

TABLE 14.9 The setOnClickPendingIntent method of the RemoteViews class

Method Description
void setOnClickPendingIntent( int viewId, PendingIntent pendingIntent ) When the user clicks on the View whose id is viewId, pendingIntent is launched.

TABLE 14.10 The getBroadcast Method of the PendingIntent class

Method Description
static PendingIntent getBroadcast( Context context, int requestCode, Intent intent, int flags ) Creates and returns a PendingIntent in the Context context; requestCode is currently not used ( use 0 as default value ); intent is the Intent to be broadcast; flags may be one of the FLAG_. . . constants.

TABLE 14.11 Selected constants of the PendingIntent class

Constant Description and Use
FLAG_UPDATE_CURRENT Pending intent, if it exists, is reused but its extras are replaced with the extras of the new intent.
FLAG_ONE_SHOT Pending intent can only be used once.
FLAG_CANCEL_CURRENT Pending intent, if it exists, is cancelled before a new one is generated.

EXAMPLE 14.8 shows the updated TemperatureProvider class. At lines 29–33, we create the Intent intent of the type TemperatureProvider (line 30), set its action so that it updates the widget defined by the TemperatureProvider class (line 31), and put the array of widget ids as extras in it (line 32–33). When the intent is launched, that triggers a call to the onUpdate method of TemperatureProvider.

At lines 35–36, we create a PendingIntent (the PendingIntent class is imported at line 3) using intent and we specify to reuse the current intent and only update the extras. The extras contain the array of widget ids, and it is possible that we delete existing widgets or install more widgets of the TemperatureProvider type on our device. Thus, the list of widget ids can vary. At line 37, we specify that clicking on the View whose id is display will trigger the pending intent to be launched.

EXAMPLE 14.8 The TemperatureProvider class, app Version 3

FIGURE 14.10 shows our widget, Version 3, running inside the tablet (second from the top on the left) alongside widgets from Versions 0, 1, and 2. If we touch the Version 3 widget, the data and time are updated, as opposed to Versions 0, 1, and 2 for which the data is static.

FIGURE 14.10 The Versions 0, 1, 2, and 3 widgets running inside the Home screen of the tablet

14.5 Retrieving the Temperature Data from a Remote Source, Temperature Widget, Version 4

In Version 4, we display live temperature data that we retrieve from a remote source. In order to do that, we need to do the following:

  • ▸ Identify a remote source that supplies the data we are looking for.

  • ▸ Identify what data to pass and how to pass it to the remote source.

  • ▸ Understand the data returned by the remote source and how it is formatted.

  • ▸ Retrieve data from the remote source:

    • Connect to the remote source.

    • Read data from the remote source into an appropriate data structure.

  • ▸ Parse that data structure so that we retrieve the data we want.

  • ▸ Display the desired data inside the widget (or the app).

Weather data is collected at weather stations. In the United States, there are around 2,000 weather stations, and each weather station has an id and geographical data associated with it, such as address, latitude, longitude, etc. The National Weather Service (NWS) provides an abundance of free weather data. In order to get weather data for a particular location, we first retrieve a list of weather stations close to that location from NWS, calculate the distances from the location to each weather station, choose the weather station that is the closest, and read data from that weather station.

In addition to NWS, there are many available sources of weather data: some are free, some are not, some require a key to get access, some do not. They may accept one or several types of input, such as zip code, city, or latitude and longitude coordinates, and they use different ways to format data, such as XML or JavaScript Object Notation (JSON), which is a lightweight way of formatting data. JSON is often used to format data transmitted over the Internet between a server and client and vice versa, because it is not as cumbersome as XML and is easy to parse.

A JSON string can include two data structures:

  • ▸ An object representing a hashtable.

  • ▸ An array.

An object is enclosed in curly braces ({}) and consists of a list of name/value pairs separated by a comma (,). A colon (:) separates the name and the value. Values can be strings enclosed in double quotes, numbers, true, false, null, an object, or an array (i.e., those two data structures can be nested). An array is a list of comma-separated values enclosed in square brackets ([]).

Here are some examples of valid JSON strings:

{ "name": "mike", "age": 22 }
{ "states": { "MD": "Maryland", "CA": "California", "TX": "Texas" } }
[ "Chicago", "New York", "Palo Alto" ]
{ "countries": [ "USA", "Mexico", "China" ] }

In this app, we use openweathermap.org as our source of weather data. It is very easy to obtain a key and it is free for limited use. The URL accepts a simple input such as a city, country string, and its output is a JSON string. One of the possible URL formats to query that site is:

http://api.openweathermap.org/data/2.5/weather?q=city,country&appid=your_key

The key is a long String made up of hexadecimal digits, for example:

8f4fc1b7ccx025gga22ac2db344a3hj8z

The URL also accepts a US state instead of a country. The String after the question mark character (?) (i.e., q=city,country&appid=key in this case) is called the Query String.

For example, we could use:

http://api.openweathermap.org/data/2.5/weather?q=London,UK&appid=YOUR_KEY
http://api.openweathermap.org/data/2.5/weather?q=New York,NY&appid=YOUR_KEY
http://api.openweathermap.org/data/2.5/weather?q=Baltimore,MD&appid=YOUR_KEY

If we open a browser and paste the first of these three examples (using an actual key) into the URL field, the browser displays the JSON string shown in FIGURE 14.11.

FIGURE 14.11 JSON String from openweathermap.org for London, UK

TABLE 14.12 Temperature conversions between Kelvin, Celsius, and Fahrenheit degrees

Kelvin Celsius Fahrenheit
K C = K – 273.15 F = ( C * 9/5) + 32
273.15 0 32
298.15 25 77

There is more information returned than we actually want for this widget. We keep the widget simple and only show the current temperature. Figure 14.11 indicates a temperature of 283.09, the value for the field temp. The temperature is given in degrees Kelvin. TABLE 14.12 shows the conversion formulas between the various temperature scales.

As we build a Model to parse such a JSON string, we note that our Model depends on the formatting of the data that we receive. Although it is not expected that the data source will change its data format frequently, it is a good idea to subscribe to notifications from the data source in order to be able to edit our code as quickly as possible to adapt to any change in the data and its formatting. The JSONObject class, part of the org.json package, includes methods to parse a JSON string. TABLE 14.13 shows some of them.

In Figure 14.11, the value mapped to coord is a JSONObject. The value mapped to weather is a JSONArray. The value mapped to name is a String, London. The constructor and the methods in Table 14.13 throw a JSONException. It is a checked exception so we must use try and catch blocks when calling these methods. Assuming that we have created a JSONObject named jsonObject with the JSON string shown in Figure 14.11, we can retrieve these values as follows:

// jsonObject is a JSONObject created from the String in Figure 14.11
try {
  JSONObject coordJSONObject = jsonObject.getJSONObject( "coord" );
  JSONArray weatherJSONArray = jsonObject.getJSONArray( "weather" );
  String city = jsonObject.getString( "name" );
} catch( JSONException jsonE ) {
  // handle the exception here
}

TABLE 14-13 Selected methods of the JSONObject class

Constructor and Methods DescriptionJSONObject( String json )
Creates a JSONObject object for json.JSONObject getJSONObject( String name ) Returns the JSONObject mapped by name if there is one and it is a JSONObject.JSONArray getJSONArray( String name )
Returns the JSONArray mapped by name if there is one and it is a JSONArray.int getInt( String name ) Returns the int value mapped by name if there is one and it can be cast to an int.double getDouble ( String name )
Returns the double value mapped by name if there is one and it can be cast to a double.String getString( String name ) Returns the String value mapped by name if there is one.

In order to retrieve the current temperature within the JSON string of Figure 14.11, we can use the following code sequence:

// jsonObject is a JSONObject created from the String in Figure 14.11
try {
  JSONObject mainJSONObject = jsonObject.getJSONObject( "main" );
  double temperature = mainJSONObject.getDouble( "temp" );
  // process temperature here
} catch( JSONException jsonE ) {
  // handle the exception here
}

EXAMPLE 14.9 shows the TemperatureParser class, part of our Model for this app. Its main functionality is to extract the temperature data from the JSON String. The constructor, at lines 13–18, instantiates jsonObject, the only instance variable, calling the JSONObject constructor and passing its String parameter. The getTemperatureK method, at lines 20–27, returns the value associated with the key temp inside the json object associated with the key main. Note that we catch a generic Exception at line 24 rather than a JSONException, because jsonObject could be null. When a TemperatureParsing object is instantiated, if the argument passed to the constructor cannot be converted to a JSONObject, then jsonObject is null. Calling the getJSONObject method with a null reference would cause a NullPointerException. By catching a generic Exception, we catch either a NullPointerException or a JSONException. If an exception occurs at that time, we use the default value of 25 degrees Celsius at line 25. The getTemperatureC (lines 29–31) and getTemperatureF (33–35) methods, return the Celsius and Fahrenheit temperatures, respectively, as rounded integers.

EXAMPLE 14.9 The TemperatureParser class, app Version 4

In addition to the TemperatureParser class, we include in our Model a class to read data from a remote location, RemoteDataReader. That class is a utility class that reads data from a remote location defined by two Strings: a base URL and a query string.

In order to read data from a remote location, we do the following:

  • ▸ Connect to the remote location.

  • ▸ Get an input stream from that remote location.

  • ▸ Read that input stream into a String.

TABLE 14-14 Selected classes and methods to read data from a remote location

Class Constructor or Method Description
URL URL( String url ) Creates a URL for url.
URL URLConnection openConnection( ) Creates and returns a URLConnection with this URL.
URLConnection InputStream getInputStream( ) Returns an InputStream that reads from this URLConnection.
URLConnection void setDoInput( boolean doInputFlag ) We call this method with the argument true if we want to read, or false if we do not want to read ( the default is true ).
URLConnection void setDoOutput( boolean doOutputFlag ) We call this method with the argument true if we want to write, or false if we do not want to write ( the default is false ).
HttpURLConnection( inherits from URLConnection ) void setRequestMethod( String method ) Sets the method for this request to method—typically GET or POST. The default is GET.
HttpURLConnection( inherits from URLConnection ) void disconnect( ) Disconnects from the server located at the URL of this HttpURLConnection.
InputStream void close( ) Closes this InputStream and releases the memory resources associated with it.
InputStreamReader( inherits from Reader ) InputStreamReader( InputStream is ) Constructs an InputStreamReader to read from the input stream is.
BufferedReader BufferedReader( Reader r ) Constructs a BufferedReader, and uses buffering for more efficient reading.
BufferedReader String readLine( ) Reads and returns a line of text.
BufferedReader void close( ) Closes the input stream and releases the memory resources associated with it.

TABLE 14.14 shows various classes and methods that we use to perform those steps. HttpURLConnection is a subclass of URLConnection with added support for the HTTP protocol. InputStreamReader is a subclass of Reader, thus, an InputStreamReader reference that can be used as the argument of the BufferedReader constructor.

EXAMPLE 14.10 shows the RemoteDataReader class. The instance variable urlString, declared at line 12, represents a full url. The constructor, at lines 14–25, constructs urlString, concatenating its four String parameters: the second one is a query string and may contain characters, such as the space or comma characters,that are considered unsafe and need to be encoded. The third and fourth parameters may be null, in case there is no key needed to read data from that URL. We encode the second parameter to URL standards at line 18 before concatenating it to baseUrl. If the third and fourth parameters are not null (line 19), we add the & character, encode it, and concatenate them to form the full URL. We expect that second String parameter to represent user input in Version 5 of the widget, so we want to make sure that special characters, in particular the space character, which we are almost certain to get from user input, are encoded.

EXAMPLE 14.10 The RemoteDataReader class, app Version 4

The getData method, at lines 27–53, reads data from the URL location represented by urlString and returns it. We open a connection at lines 29–32 and create a BufferedReader object at lines 34–37 that we use to read the data at that URL location. We initialize dataRead, a String that accumulates the data read, at line 40 and prime the read at line 41. We use a while loop at lines 42–45 to read the data into dataRead, which we return at line 49. We catch any exception that may happen during the whole process at line 50 and return the empty String at line 51 if an exception occurs.

We have completed the Model for this version. Now we need to edit the Controller for the widget, which is the TemperatureProvider class. For this version of our widget, we go on the Internet and retrieve some data from a remote server. Thus, we include two uses-permission elements in the AndroidManifest.xml file inside the manifest element as follows:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Because we are accessing the Internet, we need to do this on a separate thread. We create the TemperatureTask class, a subclass of the AsyncTask class, in order to read temperatures from a remote server. Appendix D explains the AsyncTask in great detail. The AsyncTask class uses three generic types that we must specify when extending it. The class header of a subclass is as follows:

AccessModifier ClassName extends AsyncTask<Params, Progress, Result>

TABLE 14-15 Selected methods of the AsyncTask class

Method Description
AsyncTask execute( Params. . . params ) params represent an array of values of type Params, a generic type.
Result doInBackground( Params. . . params ) params is the argument passed to execute when we call it.
void onPostExecute( Result result ) is automatically called after doInBackground finishes; result is the value returned by doInBackground.

where Params, Progress, and Result are placeholders for actual class names and have the following meanings:

  • Params is the data type of the array that is passed to the execute method of AsyncTask when we call it.

  • Progress is the data type used for progress units as the task executes in the background. If we choose to report progress, that data type is often Integer or Long.

  • Result is the data type of the value returned upon execution of the task.

We expect the retrieval of the data to be fast and, therefore, we do not intend to give the user any feedback about its progress. The input to our task is a URL string, and the output of our task is a String that we retrieve from the remote server. Thus, the class header of TemperatureTask is:

public class TemperatureTask extends AsyncTask<String, Void, String>

After we call the execute method with a TemperatureTask reference, the doInBackground method executes and its argument is the same as the one we passed to execute. Its return value becomes the argument of onPostExecute, which is called automatically after doInBackground finishes. TABLE 14.15 shows these three methods. The execute and doInBackground method headers show that they both accept a variable number of arguments.

We expect to instantiate a TemperatureTask object and call its execute method from the onCreate method of the TemperatureProvider class. The doInBackground method executes and its return value becomes the argument of the postExecute method. Thus, we need to update the widget from inside the postExecute method. However, in order to update the widget, we need to use the parameters of the onCreate method. In order to have access to these parameters inside the onPostExecute, we pass them to the TemperatureTask constructor and assign them to instance variables of the TemperatureTask class. From the postExecute method, we can then call a method of the TemperatureProvider class and pass these references to that method so it can update the widget. Note that in order to call a method of the TemperatureProvider class, we need a reference to a TemperatureProvider object, which we can also pass to the TemperatureTask constructor and assign it to an instance variable.

EXAMPLE 14.11 shows the TemperatureTask class. We declare three instance variables to match the three parameters of the onCreate method of TemperatureProvider at lines 9–11. We also declare a TemperatureProvider instance variable at line 8, so that we can call a method of the TemperatureProvider class with it. The constructor, at lines 13–21, assigns four parameter references to these four instance variables. We will call it from the onCreate method of the TemperatureProvider class using this as the first argument, and the three parameters of the onCreate method as the other three arguments.

EXAMPLE 14.11 The TemperatureTask class, app Version 4

The doInBackground method, which is automatically called after the execute method is called and given the same arguments as the ones passed to execute, is coded at lines 23–30. It instantiates a RemoteDataReader object at lines 32–33, and reads the data at that URL location into a String named json (line 34), and returns it.

The postExecute method, at lines 38–42, instantiates a TemperatureParser object with its String parameter (the String returned by the doInBackground method) at line 39. It retrieves the temperature value in Fahrenheit and calls the updateWidget method of the TemperatureProvider class at lines 40–41, passing the temperature and the three instance variables that are references to the three parameters of the onCreate method.

EXAMPLE 14.12 shows the TemperatureProvider class. We include the constants DEGREE and STARTING_URL at lines 14–16. Instead of using a resource for the city value, we declare an instance variable named city at line 19. We will need it in Version 5, when we allow the user to modify it. We declare a constant and an instance variable for the key name and the key value at lines 17 and 20. Note that for privacy and security reasons, we have substituted the key value for YOUR KEY HERE.

EXAMPLE 14.12 The TemperatureProvider class, app Version 4

The onUpdate method (lines 22–30) instantiates a TemperatureTask object at lines 27–28 and calls its execute method at line 29. The four arguments of execute are automatically passed to the doInBackground method of the TemperatureTask class.

The updateWidget method is at lines 32–56. It is called by the postExecute of the TemperatureTask class with the retrieved temperature as the first argument and the parameters of onUpdate as the other three arguments. It builds the String to display, including the temperature, at lines 34–37. The rest of the code is the same as the code in the onUpdate method of Version 3.

TABLE 14.16 Model-View-Controller classes and files, Version 4

Model RemoteDataReader.java, TemperatureParser.java
View widget_layout.xml
Controller TemperatureProvider.java, TemperatureTask.java

FIGURE 14.12 The Versions 0, 1, 2, 3, and 4 widgets running inside the Home screen of the tablet

TABLE 14.16 shows the various components of our widget, Version 4: the Model is comprised of the RemoteDataReader and TemperatureParser classes, and they are both reusable in other projects. The widget_layout.xml file is the View. The TemperatureProvider and TemperatureTask classes make up the Controller.

FIGURE 14.12 shows our widget, Version 4, running inside the tablet (third from the top on the left) alongside widgets from Versions 0, 1, 2, and 3. Notice that the temperature shows live data (77°F), not a hard coded value as with the previous versions.

14.6 Using an Activity to Customize the Widget, Temperature Widget, Version 5

In Version 4, the location is hard coded to be New York, NY. In Version 5, we allow the user to specify the location for which we show the current temperature. When the user installs the widget, he or she can set the city and state or the city and country. In this way, we allow the user to configure the widget. We can do this by adding an activity to the project. This activity runs first and collects user input. We use the user input, in this case the city and state (or city and country) to set the appropriate parameters of the widget provider. In this case, we set the value of the city variable inside the TemperatureProvider class.

In order to configure the widget, we modify our project as follows:

  • ▸ We add an activity element in the AndroidManifest.xml file.

  • ▸ We create a layout XML file for the activity that collects user input.

  • ▸ We add an android:configure attribute to the AppWidgetProviderInfo XML file.

  • ▸ We create a class for the activity.

  • ▸ We provide a mechanism to pass data from the activity to the TemperatureProvider class.

  • ▸ We update the TemperatureProvider class as necessary.

EXAMPLE 14.13 shows the updated AndroidManifest.xml file. At lines 16–21, we add an activity element inside the application element and before the receiver element for the widget. At line 16, we specify.TemperatureWidgetConfigure as the value for the android:name attribute of the activity element. That means that the name of our class that extends the Activity class is TemperatureWidgetConfigure. The dot (.) in front of it means that it is located in the current package. At lines 18–19, we specify that the action for the intent of the activity is to configure a widget by assigning the value of the ACTION_APPWIDGET_CONFIGURE constant of the AppWidgetManager class (see TABLE 14.17) to the android:name attribute of the action element of the intent-filter element of the activity element. The widget host, in this case the Home screen, launches the activity with an ACTION_APPWIDGET_CONFIGURE action, so we need to specify this in the manifest.

EXAMPLE 14.13 The AndroidManifest.xml file, app Version 5

TABLE 14.17 Selected constants of the AppWidgetManager class

Constant Description
ACTION_APPWIDGET_CONFIGURE Action sent to start the activity specified in the AppWidgetProviderInfo metadata.
EXTRA_APPWIDGET_ID Use this constant to retrieve the widget id from the extras of an intent.
INVALID_APPWIDGET_ID Has value 0, a value that an AppWidgetManager never returns as a valid widget id.

The widget_config.xml file, which we create in the layout directory, defines the layout for the activity that configures the widget. The user interface includes a text field to enter data and a button to process the data entered. EXAMPLE 14.14 shows the widget_config.xml file. At lines 6–10, we code a TextView element telling the user what to do. The EditText element is defined at lines 11–15 and is given the id city_input at line 12 so that we can retrieve it by code using the findViewById method. The button is defined at lines 16–21. At line 20, we specify configure as the method to execute when the user clicks on the button.

EXAMPLE 14.14 The widget_config.xml file, app Version 5

It is mandatory to declare the activity in the AppWidgetProviderInfo XML file using the android:configure attribute. Thus, we update the widget_info.xml file, shown in EXAMPLE 14.15. At lines 8–9, we specify the TemperatureWidgetConfigure class of the com.jblearning.temperaturewidgetv5 package as the value for the android:configure attribute of the appwidget-provider element.

EXAMPLE 14.15 The widget_info.xml file, app Version 5

Next, we create TemperatureWidgetConfigure, the activity class that configures the widget. In order to configure a widget inside an activity, we do the following:

  • ▸ Get user input as needed.

  • ▸ Get the widget id.

  • ▸ Update the widget.

  • ▸ Create the return intent.

  • ▸ Pass the user input data to the AppWidgetProvider class.

  • ▸ Exit the activity.

When we use a configuration activity to customize a widget, it is the responsibility of the activity to first update the widget. Also, the configuration activity should return a result that should include the widget id that was passed by the intent that launched the activity.

EXAMPLE 14.16 shows the TemperatureWidgetConfigure class. At line 15, we set the result to the value of the RESULT_CANCELED constant of the Activity class, shown in TABLE 14.18. When the result is set to RESULT_CANCELED, the widget is not installed if the user does not complete the configuration activity.

EXAMPLE 14.16 The TemperatureWidgetConfigure class, app Version 5

TABLE 14.18 Constants of the Activity class

Constant Description
RESULT_CANCELED 0: means that the activity was cancelled.
RESULT_OK –1: means that the activity ended successfully.

At lines 20–22, we retrieve user input. To pass simple data from an activity to another, we typically use the putExtra and getExtra methods of the Intent class. However, there is no intent associated with the AppWidgetProvider class, so we cannot use that strategy here. Furthermore, the default constructor of an AppWidgetProvider class is called automatically and re-initializes the instance variables to their default values. Thus, it is not practical to store widget data in an instance variable whose value can change over time. In Version 4, we used the instance variable city of the TemperatureProvider class, declared at line 19 of Example 14.12, to store the city and state (or country), even though it is hard coded to the value New York, NY for that version. In Version 5, we make that variable global by specifying it as public and static so that we can access it and modify it from the TemperatureWidgetConfigure class. That is a very simple and convenient way of passing data from an activity class to a widget provider class. At lines 24–25, we access the public static variable city of the TemperatureProvider class and update it with the value input by the user.

At lines 27–34, we retrieve the widget id from the incoming intent. We set its default value to 0 (the value of the INVALID_APPWIDGET_ID constant, shown in Table 14.17) at line 30. At line 31, we test if there is some data stored in the Bundle of the incoming intent. If there is, we attempt to retrieve the widget id at lines 32–34. If the Bundle extras has a key entry equal to the value of the EXTRA_APPWIDGET_ID constant of the AppWidgetManager class (see Table 14.17), then that key maps to the widget id value and that widget id value is returned by calling getInt. If not, the call to getInt returns the second argument, the value of INVALID_APPWIDGET_ID (i.e., 0).

We test if the widget id value is valid at line 36. If it is, we update the widget at lines 37–43, create a return intent at lines 45–46, place the widget id in it at lines 48–49, and set the result to the value of RESULT_OK at line 50. After that, or if the widget id value is invalid, we terminate the activity at lines 53–54.

EXAMPLE 14.17 shows the TemperatureProvider class. At line 19, we change the access modifier to the city variable from private to public and static so we can modify its value in the TemperatureWidgetConfigure class (see line 25 of Example 14.16).

EXAMPLE 14.17 The TemperatureProvider class, app Version 5

TABLE 14.19 The sequence of method calls when installing the widget, Version 5

Event and Method Call Sequence Widget id Method Call
Install the widget
TemperatureProvider:onReceive Invalid Automatic
TemperatureProvider:onUpdate Automatic
TemperatureWidgetConfigure:onCreate Automatic
Enter the city and state, click on CONFIGURE WIDGET
TemperatureWidgetConfigure:configure Valid Event handling
TemperatureProvider:onReceive Valid Automatic
TemperatureProvider:onUpdate Called by onReceive

The onReceive method of the AppWidgetProvider class is automatically called after the TemperatureWidgetConfigure activity finishes. It is coded at lines 58–70. At lines 62–64, we use its Intent parameter in order to retrieve the app widget id. If it is a valid widget id (line 66), we update the widget by calling the onUpdate method at line 68. TABLE 14.19 shows the sequence of method calls and the nature of the method calls.

TABLE 14.20 shows the state of the Model, View, and Controller for our widget Version 5. As compared to Version 4, the Model is unchanged. We added the widget_config.xml file to the View to define the layout of the activity that configures the widget, and we added the TemperatureWidgetConfigure class and modified the TemperatureProvider class in the Controller.

FIGURE 14.13 shows the user configuring the city and state during the configuration of the widget, Version 5. FIGURE 14.14 shows our widget, Version 5, running inside the tablet alongside widgets from Versions 0, 1, 2, 3, and 4. The city and state of the widget, Version 5 (San Francisco, CA), are different from the others, and it shows live data like Version 4.

TABLE 14.20 Model-View-Controller classes and files, Version 5

Model RemoteDataReader.java, TemperatureParser.java
View widget_layout.xml, widget_config.xml
Controller TemperatureProvider.java, TemperatureTask.java, TemperatureWidgetConfigure.java

FIGURE 14.13 The configure screen when installing the widget, Version 5

FIGURE 14.14 The Versions 0, 1, 2, 3, 4, and 5 widgets running inside the Home screen of the tablet

14.7 Hosting the Widget in the Lock Screen, Temperature Widget, Version 6

Since Android Version 4.2, it is also possible to host a widget on the Lock screen, also called the Keyguard. We enable hosting of a widget on the Lock screen by assigning the value keyguard to the android:widgetCategory attribute of the appwidget-provider element in the widget info file as follows:

TABLE 14.21 Selected constants of the AppWidgetProviderInfo class

Constant Description
WIDGET_CATEGORY_HOME_SCREEN Has value 1. Widget can be displayed on the Home screen.
WIDGET_CATEGORY_KEYGUARD Has value 2. Widget can be displayed on the Keyguard ( Lock screen ).
android:widgetCategory="keyguard"

We assign the values keyguard, home_screen, or keyguard | home_screen values to the android:widgetCategory attribute, depending on whether we want to allow the Lock screen, the Home screen, or both to be possible Host screens for the widget. If none is specified, the default host for the widget is the Home screen. These values correspond to the WIDGET_CATEGORY_KEYGUARD and WIDGET_CATEGORY_HOME_SCREEN constants of the AppWidgetProviderInfo class, shown in TABLE 14.21.

EXAMPLE 14.18 shows the updated widget_info.xml file. At line 8, we specify that the widget can be hosted on either the Home screen or the Lock screen.

EXAMPLE 14.18 The widget_info.xml file, app Version 6

It is possible to design a widget that has a different appearance on the Lock screen from its appearance on the Home screen. For this, we create a different XML layout file for the widget when it is hosted on the Lock screen. We also need to specify the name of that file in the AppWidgetProviderInfo XML file. If the name of the XML layout file for the Lock screen is widget_layout_keyguard.xml, then we add the following attribute/value pair in the appwidget-provider element of the AppWidgetProviderInfo XML file.

TABLE 14.22 The OPTION_APPWIDGET_HOST_CATEGORY constant and the getAppWidgetOptions method of the AppWidgetManager class

Constant and Method Description
OPTION_APPWIDGET_HOST_CATEGORY A String constant used as a key to retrieve the value of the widget host category stored as an extra.
Bundle getAppWidgetOptions( int widgetId ) Returns the Bundle storing the extras for the widget whose id is widgetId.

TABLE 14.23 The getInt method of the BaseBundle class, inherited by Bundle

Method Description
int getInt( String key ) Returns the integer value associated with key, 0 if there is none.
android:initialKeyguardLayout="@layout/widget_layout_keyguard"

Inside the AppWidgetProvider class, we can test if the widget host is the Home screen or the Lock screen. The host information is stored in the Bundle associated with the widget. Inside the onUpdate method of the AppWidgetProvider class, we have an AppWidgetManager reference as a parameter of that method. We can retrieve that Bundle by calling the getAppWidgetOptions method of the AppWidgetManager class (see TABLE 14.22), passing the widget id as its only argument. With the Bundle reference, we can call the getInt method, shown in TABLE 14.23, passing the OPTION_APPWIDGET_HOST_CATEGORY constant of the AppWidgetManager class (see Table 14.22), and retrieve the widget host category as an int. We can then compare that integer value to the WIDGET_CATEGORY_HOME_SCREEN and WIDGET_CATEGORY_KEYGUARD constants of the AppWidgetProviderInfo class (see Table 14.21), whose values are 1 and 2 respectively, in order to test what host the widget is on.

We can use the following sequence to test if the host is the Home screen or the Lock screen.

Bundle bundle = appWidgetManager.getAppWidgetOptions( widgetId );
int host =
  bundle.getInt( AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);
if( host == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD ) {
  // code for Lock screen host
} else if( host == AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN ) {
  // code for Home screen host
}

Designing a widget that looks and behaves differently on the Lock screen is left as an exercise.

Chapter Summary

  • App widgets, also known as widgets, are small apps that are embedded in other apps, called app widget hosts.

  • The Home screen and the Lock screen are examples of app widget hosts.

  • To create a widget, we can extend the AppWidgetProvider class and override its onUpdate method.

  • The widget info file, located in the xml directory of the res directory, defines an appwidget-provider element that specifies the characteristics of a widget such as size and frequency of updates.

  • Not all View and layout manager classes can be used inside a widget.

  • A widget can use a drawable resource for its background.

  • A widget can update its display automatically at a certain frequency. The minimum frequency is 30 minutes.

  • We can update the widget via user interaction with the widget, such as a click.

  • It is possible to update the data displayed by the widget dynamically. That data can be retrieved locally or from a remote source.

  • A widget can be customizable by the user at installation by adding an activity that captures user input.

  • Not only can we install a widget on the Home screen, we can also install a widget on the Lock screen.

Exercises, Problems, and Projects

Multiple-Choice Exercises

  1. What method of AppWidgetProvider do we override to update a widget?

    • update

    • onUpdate

    • widgetUpdate

    • broadcast

  2. The third parameter of the method in question 1 is

    • A Context reference

    • An AppWidgetManager reference

    • A widget id

    • An array of widget ids

  3. What class do we use to manage the View hierarchy inside a widget?

    • Views

    • View

    • RemoteViews

    • Remotes

  4. What element do we define in the widget info XML file?

    • appwidget

    • appwidget-provider

    • provider

    • widget-info

  5. The data type of a widget id is

    • A char

    • A String

    • An int

    • An AppWidgetInfo

  6. The minimum frequency of automatic update for a widget is

    • 1 second

    • 30 seconds

    • 60 seconds

    • 30 minutes

  7. We can allow a widget to be updated via user interaction

    • No, that is not possible

    • Yes, by clicking on it for example

    • Yes, but only every 60 seconds

    • Yes, but only every 30 minutes

  8. The data displayed by a widget

    • cannot be changed

    • can be changed, but only using data stored inside the device

    • can be changed, by retrieving data from an external source

    • can only be changed every 30 minutes

  9. How can we customize a widget at installation time?

    • It is not possible

    • We can run an activity at that time and capture and use user input to customize the widget

    • We use the CustomizeWidget class of the android.widget package

    • We use the SpecializeWidget class of the android.widget package

  10. What are examples of a Host screen for a widget?

    • There is no such thing as a Host screen for a widget

    • The Home screen and only the Home screen

    • The Lock screen and only the Lock screen

    • The Home screen and the Lock screen

Fill in the Code

  1. Write the code so that the widget’s update frequency is 1 hour

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/widget_layout"
        android:minHeight="40dp"
        android:minWidth="180dp"
        <!-- your code goes here -->
    
    </appwidget-provider>
    
  2. Inside the onUpdate method of an AppWidgetProvider class, update all the widgets of the type of that AppWidgetProvider class.

    public void onUpdate ( Context context,
           AppWidgetManager appWidgetManager, int[ ] appWidgetIds ) {
      super.onUpdate( context, appWidgetManager, appWidgetIds );
    RemoteViews views = new RemoteViews( context.getPackageName( ), 
                                            R.layout.widget_layout );
      // Your code goes here
    
    }
    
  3. Write the code to set the text inside a TextView whose id is my_view to HELLO WIDGET.

    RemoteViews remoteViews = new RemoteViews( context.getPackageName( ),
                                               R.layout.widget_layout );
    // Your code goes here
    
  4. Inside the onUpdate method of the MyProvider class, write the code to update the widget every time the user clicks on a View whose id is my_view inside the widget.

    RemoteViews remoteViews = new RemoteViews( context.getPackageName( ),
                                               R.layout.widget_layout );
    // widgetId is the id of the current widget
    Intent intent = new Intent( context, MyProvider.class );
    intent.setAction( AppWidgetManager.ACTION_APPWIDGET_UPDATE );
    intent.putExtra( AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds );
    // Your code goes here
    
    appWidgetManager.updateAppWidget( widgetId, remoteViews );
    
  5. Consider the following JSON string named json. Write the code to retrieve the value of a country (i.e., Italy).

    {"coord":{"lon":12.495800018311,"lat":41.903049468994},"sys":{"country":
    "Italy","sunrise":1374033016,"sunset":1374086531 } };
    
    // Your code goes here
    
  6. Write the code so that the widget is configured by an activity named MyActivity and located in the com.xyz.q16 package.

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/widget_layout"
        android:minHeight="40dp"
        android:minWidth="180dp"
        <!-- your code goes here -->
    
    </appwidget-provider>
    
  7. Inside a configuration activity, write the code to put current_widget_id, a widget id, as an extra stored in the Intent resultIntent.

    // the app widget id is widget_id
    Intent resultIntent = new Intent( );
    // Your code goes here
    
  8. Write the code so that the widget can be installed on the Home screen or the Lock screen.

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/widget_layout"
        android:minHeight="40dp"
        android:minWidth="180dp"
        <!-- your code goes here -->
    
    </appwidget-provider>
    

Write a Widget

  1. Modify Version 4 of the example in the chapter so that it displays something different from the temperature, for example, the humidity level.

  2. Modify Version 4 of the example in the chapter so that it displays two pieces of data, not one, and different from the temperature, for example, the pressure and the humidity level.

  3. Modify Version 5 of the example in the chapter so that it displays the temperature for two cities, not one.

  4. Modify Version 5 of the example in the chapter so that it displays data for two cities, not one, and that data should be different from the temperature, for example, the humidity level.

  5. Modify Version 5 of the example in the chapter so that the user can customize how the temperature is displayed, not the location. The user can choose between degrees Celsius and degrees Fahrenheit.

  6. Modify Version 5 of the example in the chapter so that the user can customize the background color of the widget, not the location.

  7. Create a widget of your choice that pulls data from a remote source of your choice.

  8. Create a widget of your choice that pulls data from a remote source of your choice and is customizable by the user.

  9. Modify Version 5 of the example in the chapter so that the data is pulled from the NWS. Use the strategy described at the beginning of paragraph 14.5 of the chapter.

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

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