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.
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:
▸ 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.
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
.
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.
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).
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.
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).
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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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
.
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 |
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.
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.
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.
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.
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.
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).
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 |
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.
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.
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.
What method of AppWidgetProvider do we override to update a widget?
update
onUpdate
widgetUpdate
broadcast
The third parameter of the method in question 1 is
A Context reference
An AppWidgetManager reference
A widget id
An array of widget ids
What class do we use to manage the View hierarchy inside a widget?
Views
View
RemoteViews
Remotes
What element do we define in the widget info XML file?
appwidget
appwidget-provider
provider
widget-info
The data type of a widget id is
A char
A String
An int
An AppWidgetInfo
The minimum frequency of automatic update for a widget is
1 second
30 seconds
60 seconds
30 minutes
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
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
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
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
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>
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 }
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
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 );
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
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>
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
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>
Modify Version 4 of the example in the chapter so that it displays something different from the temperature, for example, the humidity level.
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.
Modify Version 5 of the example in the chapter so that it displays the temperature for two cities, not one.
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.
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.
Modify Version 5 of the example in the chapter so that the user can customize the background color of the widget, not the location.
Create a widget of your choice that pulls data from a remote source of your choice.
Create a widget of your choice that pulls data from a remote source of your choice and is customizable by the user.
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.
3.144.117.167