Objectives
In this chapter you’ll:
Use the free OpenWeatherMap.org
REST web services to get a 16-day weather forecast for a city specified by the user.
Use an AsyncTask
and an HttpUrlConnection
to invoke a REST web service or to download an image in a separate thread and deliver results to the GUI thread.
Process a JSON response using package org.json
classes JSONObject
s and JSONArray
s.
Define an ArrayAdapter
that specifies the data to display in a ListView
.
Use the ViewHolder
pattern to reuse views that scroll off the screen in a ListView
, rather than creating new views.
Use the material design components TextInputLayout
, Snackbar
and FloatingActionButton
from the Android Design Support Library.
7.2 Test-Driving the WeatherViewer App
7.3.2 JavaScript Object Notation (JSON) and the org.json
Package
7.3.3 HttpUrlConnection
Invoking a REST Web Service
7.3.4 Using AsyncTask
to Perform Network Requests Outside the GUI Thread
7.3.5 ListView
, ArrayAdapter
and the View-Holder Pattern
7.4 Building the App’s GUI and Resource Files
7.5.1 package
Statement, import
Statements and Instance Variables
7.5.3 Method convertTimeStampToDay
7.6.1 package
Statement and import
Statements
7.6.3 Instance Variable and Constructor
7.6.4 Overridden ArrayAdapter
Method getView
7.6.5 AsyncTask
Subclass for Downloading Images in a Separate Thread
7.7.1 package
Statement and import
Statements
7.7.3 Overridden Activity Method onCreate
7.7.4 Methods dismissKeyboard
and createURL
7.7.5 AsyncTask
Subclass for Invoking a Web Service
7.7.6 Method convertJSONtoArrayList
Self-Review Exercises | Answers to Self-Review Exercises | Exercises
The WeatherViewer app (Fig. 7.1) uses the free OpenWeatherMap.org
REST web services to obtain a specified city’s 16-day weather forecast. The app receives the weather data in JSON (JavaScript Object Notation) data format. The list of weather data is displayed in a ListView
—a view that displays a scrollable list of items. In this app, you’ll use a custom list-item format to display:
• a weather-condition icon
• the day of the week with a text description of that day’s weather
• the day’s low and high temperatures (in °F), and
• the humidity percentage.
The preceding items represent a subset of the returned forecast data. For details of the data returned by the 16-day weather forecast API, visit:
For a list of all weather data APIs provided by OpenWeatherMap.org
, visit:
Open Android Studio and open the WeatherViewer app from the WeatherViewer
folder in the book’s examples folder. Before running this app, you must add your own OpenWeatherMap.org
API key. See Section 7.3.1 for information on how to obtain your key and where you should place it in the project. This is required before you can run the app. After adding your API key to the project, execute the app in the AVD or on a device.
When the app first executes, the EditText
at the top of the user interface receives the focus and the virtual keyboard displays so you can enter a city name (Fig. 7.2). You should consider following the city with a comma and the country code. In this case, we entered New York, NY, US
to locate the weather for New York, NY in the United States. Once you’ve entered the city, touch the circular FloatingActionButton
containing the done icon () to submit the city to the app, which then requests that city’s 16-day weather forecast (shown in Fig. 7.1).
This section introduces the features you’ll use to build the WeatherViewer app.
This chapter introduces web services, which promote software portability and reusability in applications that operate over the Internet. A web service is a software component that can be accessed over a network.
The machine on which a web service resides is the web service host. The client—in this case the WeatherViewer app—sends a request over a network to the web service host, which processes the request and returns a response over the network to the client. This distributed computing benefits systems in various ways. For example, an app can access data on demand via a web service, rather than storing the data directly on the device. Similarly, an app lacking the processing power to perform specific computations could use a web service to take advantage of another system’s superior resources.
Representational State Transfer (REST) refers to an architectural style for implementing web services—often called RESTful web services. Many of today’s most popular free and fee-based web services are RESTful. Though REST itself is not a standard, RESTful web services use web standards, such as HyperText Transfer Protocol (HTTP), which is used by web browsers to communicate with web servers. Each method in a RESTful web service is identified by a unique URL. So, when the server receives a request, it immediately knows what operation to perform. Such web services can be used in an app or even entered directly into a web browser’s address bar.
Using a web service often requires a unique API key from the web service’s provider. When your app makes a request to the web service, the API key enables the provider to:
• confirm that you have permission to use the web service and
• track your usage—many web services limit the total number of requests you can make in a specific timeframe (e.g., per second, per minute, per hour, etc.).
Some web services require authentication before the web service gives the app an API key—in effect, you log into the web service programmatically, before being allowed to use the web service.
The OpenWeatherMap.org
web services we use in the WeatherViewer app are free, but OpenWeatherMap.org
limits the number of web service requests—these limits are currently 1200 requests-per-minute and 1.7 million requests-per-day. OpenWeatherMap.org
is a freemium service—in addition to the free tier that you’ll use in this app, they offer paid tiers with higher request limits, more frequent data updates and other features. For additional information about the OpenWeatherMap.org
web services, visit:
OpenWeatherMap.org
uses a creative commons public license for its web services. For the license terms, visit:
For more information about the license terms, see the Licenses section at
Before running this app, you must obtain your own OpenWeatherMap.org
API key from
After registering, copy the hexadecimal API key from the confirmation web page, then replace YOUR_API_KEY
in strings.xml
with the key.
JavaScript Object Notation (JSON) is an alternative to XML for representing data. JSON is a text-based data-interchange format used to represent objects in JavaScript as collections of name/value pairs represented as String
s. JSON is a simple format that makes objects easy to create, read and parse and, because it’s much less verbose than XML, allows programs to transmit data efficiently across the Internet. Each JSON object is represented as a list of property names and values contained in curly braces, in the following format:
{
propertyName1:
value1,
propertyName2:
value2}
Each property name is a String
. Arrays are represented in JSON with square brackets in the following format:
[
value1,
value2,
value3]
Each array element can be a String
, number, JSON object, true
, false
or null
. Figure 7.3 sample JSON returned by OpenWeatherMap.org
’s daily forecast web service used in this app—this particular sample contains two days of weather data (lines 15–57).
1 {
2 "city": {
3 "id": 5128581,
4 "name": "New York",
5 "coord": {
6 "lon": -74.005966,
7 "lat": 40.714272
8 },
9 "country": "US",
10 "population": 0
11 },
12 "cod": "200",
13 "message": 0.0102,
14 "cnt": 2,
15 "list": [{ // you'll use this array of objects to get the daily weather
16 "dt": 1442419200,
17 "temp":
18 "day": 79.9,
19 "min": 71.74,
20 "max": 82.53,
21 "night": 71.85,
22 "eve": 82.53,
23 "morn": 71.74
24 },
25 "pressure": 1037.39,
26 "humidity": 64,
27 "weather": [{
28 "id": 800,
29 "main": "Clear",
30 "description": "sky is clear",
31 "icon": "01d"
32 }],
33 "speed": 0.92,
34 "deg": 250,
35 "clouds": 0
36 }, { // end of first array element and beginning of second one
37 "dt": 1442505600,
38 "temp": {
39 "day": 79.92,
40 "min": 66.72,
41 "max": 83.1,
42 "night": 70.79,
43 "eve": 81.99,
44 "morn": 66.72
45 },
46 "pressure": 1032.46,
47 "humidity": 62,
48 "weather": [{
49 "id": 800,
50 "main": "Clear",
51 "description": "sky is clear",
52 "icon": "01d"
53 }],
54 "speed": 1.99,
55 "deg": 224,
56 "clouds": 0
57 }] // end of second array element and end of array
58 }
There are many properties in the JSON object returned by the daily forecast. We use only the "list"
property—an array of JSON objects representing the forecasts for up to 16 days (7 by default, unless you specify otherwise). Each "list"
array element contains many properties of which we use:
• "dt"
—a long
integer containing the date/time stamp represented as the number of seconds since January 1, 1970 GMT. We convert this into a day name.
• "temp"
—a JSON object containing double
properties representing the day’s temperatures. We use only the minimum ("min"
) and maximum ("max"
) temperatures, but the web service also returns the average daytime ("day"
), nighttime ("night"
), evening ("eve"
) and morning ("morn"
) temperatures.
• "humidity"
—an int
representing the humidity percentage.
• "weather"
—a JSON object containing several properties, including a description of the conditions ("description"
) and the name of an icon that represents the conditions ("icon"
).
You’ll use the following classes from the org.json package to process the JSON data that the app receives (Section 7.7.6):
• JSONObject—One of this class’s constructors converts a String
of JSON data into a JSONObject
containing a Map<String, Object>
that maps the JSON keys to their corresponding values. You access the JSON properties in your code via JSONObject
’s get methods, which enable you to obtain a JSON key’s value as one of the types JSONObject
, JSONArray
, Object
, boolean
, double
, int
, long
or String
.
• JSONArray—This class represents a JSON array and provides methods for accessing its elements. The "list"
property in the OpenWeatherMap.org
response will be manipulated as a JSONArray
.
To invoke the OpenWeatherMap.org
daily forecast web service, you’ll convert the web service’s URL String
into a URL
object, then use the URL
to open an HttpUrlConnection
(Section 7.7.5). This will make the HTTP request to the web service. To receive the JSON response, you’ll read all the data from the HttpUrlConnection
’s InputStream
and place it in a String
. We’ll show you how to convert that to a JSONObject
for processing.
You should perform long-running operations or operations that block execution until they complete (e.g., network, file and database access) outside the GUI thread. This helps maintain application responsiveness and avoid Activity Not Responding (ANR) dialogs that appear when Android thinks the GUI is not responsive. Recall from Chapter 6, however, that updates to an app’s user interface must be performed in the GUI thread, because GUI components are not thread safe.
To perform long-running tasks that result in updates to the GUI, Android provides class AsyncTask (package android.os
), which performs the long-running operation in one thread and delivers the results to the GUI thread. The details of creating and manipulating threads are handled for you by class AsyncTask
, as are communicating the results from the AsyncTask
to the GUI thread. We’ll use two AsyncTask
subclasses in this app—one will invoke the OpenWeatherMap.org
web service (Section 7.7.5) and the other will download a weather-condition image (Section 7.6.5).
This app displays the weather data in a ListView
(package android.widget
)—a scrollable list of items. ListView
is a subclass of AdapterView (package android.widget
), which represents a view that get’s its data from a data source via an Adapter object (package android.widget
). In this app, we use a subclass of ArrayAdapter (package android.widget
) to create an object that populates the ListView
using data from an ArrayList
collection object (Section 7.6). When the app updates the ArrayList
with weather data, we’ll call the ArrayAdapter
’s notifyDataSetChanged method to indicate that the underlying data in the ArrayList
has changed. The adapter then notifies the ListView
to update its list of displayed items. This is known as data binding. Several types of AdapterView
s can be bound to data using an Adapter
. In Chapter 9, you’ll learn how to bind database data to a ListView
. For more details on data binding in Android and several tutorials, visit
By default, a ListView
can display one or two TextView
s. In this app, you’ll customize the ListView
items to display an ImageView
and several TextView
s in a custom layout. Creating custom ListView
items involves the expensive runtime overhead of creating new objects dynamically. For large lists with complex list-item layouts and for which the user is scrolling rapidly, this overhead can prevent smooth scrolling. To reduce this overhead, as ListView
items scroll off the screen, Android reuses those list items for the new ones that are scrolling onto the screen. For complex item layouts, you can take advantage of the existing GUI components in the reused list items to increase a ListView
’s performance.
To do this, we introduce the view-holder pattern in which you create a class (typically named ViewHolder
) containing instance variables for the views that display a ListView
item’s data. When a ListView
item is created, you also create a ViewHolder
object and initialize its instance variables with references to the item’s nested views. You then store that ViewHolder
object with the ListView
item, which is a View
. Class View
’s setTag method allows you to add any Object
to a View
. This Object
is then available to you via the View
’s getTag method. We’ll specify as the tag the ViewHolder
object that contains references to the ListView
item’s nested views.
As a new item is about to scroll onto the screen, the ListView
checks whether a reusable view is available. If not, we inflate the new item’s view from a layout XML file, then store references to the GUI components in a ViewHolder
object. Then we’ll use setTag
to set that ViewHolder
object as the tag for the ListView
item. If there is a reusable item available, we’ll get that item’s tag with getTag
, which will return the existing ViewHolder
object that was created previously for that ListView
item. Regardless of how we obtain the ViewHolder
object, we’ll then display data in the ViewHolder
’s referenced views.
Users touch buttons to initiate actions. With material design in Android 5.0, Google introduced the floating action button (Google refers to this as the “FAB”) as a button that floats over the app’s user interface—that is, it has a higher material-design elevation than the rest of the user interface—and that specifies an important action. For example, a contacts app might use a floating action button containing a + icon to promote the action for adding a new contact. In this app, we use a floating action button containing a done icon () to enable the user to submit a city to the app and obtain that city’s forecast. With Android 6.0 and the new Android Design Support Library, Google formalized the floating action button as class FloatingActionButton (package android.support.design.widget
). In Android Studio 1.4, Google reimplemented the app templates to use material design, and most new template include a FloatingActionButton
by default.
FloatingActionButton
is a subclass of ImageView
, which enables a FloatingActionButton
to display an image. The material design guidelines suggest that you position a FloatingActionButton
at least 16dp
from the edges of a phone device and at least 24dp
from the edges of a tablet device—the default app templates configure this for you. For more details about how and when you should use a FloatingActionButton
, visit:
In this app, you’ll use an EditText
to enable the user to enter the city for which you’d like to obtain a weather forecast. To help the user understand an EditText
’s purpose, you can provide hint text that’s displayed when the EditText
is empty. Once the user starts entering text, the hint disappears—possibly causing the user to forget the EditText
’s purpose.
The Android Design Support Library’s TextInputLayout
(package android.support.design.widget
) solves this problem. In a TextInputLayout
, when the EditText
receives the focus, the TextInputLayout
animates the hint text from it’s original size to a smaller size that’s displayed above the EditText
so that the user can enter data and see the hint (Fig. 7.2). In this app, the EditText
receives the focus as the app begins executing, so the TextInputLayout
immediately moves the hint above the EditText
.
A Snackbar (package android.support.design.widget
) is a material design component similar in concept to a Toast
. In addition to appearing on the screen for a specified time limit, Snackbar
s are also interactive. Users can swipe them away to dismiss them. A Snackbar
also can have an associated action to perform when the user touches the Snackbar
. In this app, we’ll use a Snackbar
to display informational messages.
In this section, we review the new features in the GUI and resource files for the Weather Viewer app.
Create a new project using the template Blank Activity. In the Create New Project dialog’s New Project step, specify:
• Application name: WeatherViewer
• Company Domain: deitel.com
(or specify your own domain name)
For the remaining steps in the Create New Project dialog, use the same settings as in Section 2.3. Follow the steps in Section 2.5.2 to add an app icon to your project. Also, follow the steps in Section 4.4.3 to configure Java SE 7 support for the project.
The WeatherViewer is designed for only portrait orientation. Follow the steps you performed in Section 3.7 to set the android:screenOrientation
property to portrait
. In addition, add the following Internet-access permission to the <manifest>
element before its nested <application>
element:
<uses-permission android:name="android.permission.INTERNET" />
This allows the app to access the Internet, which is required to invoke a web service.
The new Android 6.0 permissions model (introduced in Chapter 5) automatically grants the Internet permission at installation time, because Internet access is considered a fundamental capability in today’s apps. In Android 6.0, the Internet permission and many others that, according to Google, are not “great risk to the user’s privacy or security” are granted automatically at installation time—these permissions are grouped into the category PROTECTION_NORMAL. For a complete list of such permissions, visit:
Android does not ask users to grant such permissions, nor can users revoke such permissions from the app. For this reason, your code does not need to check whether the app has a given PROTECTION_NORMAL
permission. You must still request these permissions in AndroidManifest.xml
, however, for backward compatibility with earlier Android versions.
Double click strings.xml
in the res/values
folder, then click the Open editor link to display the Translations Editor and create the String
resources in Fig. 7.4.
The Android Studio Blank Activity template customizes the app’s primary, dark primary and accent colors. In this app, we changed the template’s accent color (colorAccent
) to a blue shade (hexadecimal value #448AFF
) in colors.xml
.
The Android Studio Blank Activity template breaks MainActivity
’s GUI into two files:
• activity_main.xml
defines the activity’s Toolbar
(the app bar replacement in an AppCompatActivity
) and a FloatingActionButton
, which is positioned in the bottom-right corner by default.
• content_main.xml
defines the rest of MainActivity
’s GUI and is included in the activity_main.xml
file via an <include>
element.
Make the following changes to activity_main.xml
for this app:
1. Add the id coordinatorLayout
to the CoordinatorLayout
—you’ll use this to specify the layout in which a Snackbar
will be displayed.
2. Add the material design done () button to the project via the Vector Asset Studio (as you did in Section 4.4.9), then specify this new icon for the predefined FloatingActionButton
’s src property.
3. Edit the layout’s XML to configure several FloatingActionButton
properties that are not available via the Properties window. Change the layout_gravity
from bottom|end
to top|end
so that the FloatingActionButton
appears at the top right of the user interface.
4. To move the button to overlap the EditText
’s right edge, define a new dimension resource named fab_margin_top
with the value 90dp
. Using this dimension resource and the fab_margin
dimension resource defined by the Blank Activity template to define the following FloatingActionButton
margins:
android:layout_marginTop="@dimen/fab_margin_top"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="@dimen/fab_margin"
android:layout_marginStart="@dimen/fab_margin"
5. Finally, remove the FloatingActionButton
’s layout_margin
that was predefined by the Blank Activity template.
This layout is included into activity_main.xml
and defines MainActivity
’s primary GUI. Perform the following steps:
1. Remove the default TextView
defined by the Blank Activity template and change the RelativeLayout
to a vertical LinearLayout
.
2. Next, insert a TextInputLayout
. In the layout editor’s Design view, click CustomView in the Custom section. In the dialog that appears, begin typing TextInputLayout
to search the list of custom GUI components. Once the IDE highlights TextInputLayout
, click OK, then in the Component Tree, click the LinearLayout
to insert the TextInputLayout
as a nested layout.
3. To add an EditText
to the TextInputLayout
, switch to the layout editor’s Text view, then change the TextInputLayout
element’s closing />
to >
, position the cursor to the right of the >
, press Enter and type </
. The IDE will auto-complete the closing tag. Between the TextInputLayout
’s starting and ending tags, type <EditText
. The IDE will show an auto-complete window with EditText
selected. Press Enter to insert an EditText
, then set its layout_width
to match_parent
and layout_height
to wrap_content
. In Design view, set the EditText
’s id to locationEditText
, check its singleLine property’s checkbox and set its hint property to the String
resource hint_text
.
4. To complete the layout, drag a ListView
onto the LinearLayout
in the Component Tree. Set its layout:width to match_parent
, its layout:height to 0dp
, its layout:weight to 1
and its id to weatherListView
. Recall that the layout:height value 0dp
is recommended by the IDE for more efficient rendering when using the layout:weight to determine a View
’s height.
You’ll now add the list_item.xml
layout to the project and define the custom layout for displaying weather data in a ListView
item (Fig. 7.5). This layout will be inflated by the WeatherArrayAdapter
to create the user interface for new ListView
items (Section 7.6.4).
Create the list_item.xml
layout file by performing the following steps:
1. Right click the project’s layout
folder, and select New > Layout resource file.
2. Enter list_item.xml
in the File name field of the New Resource File dialog.
3. Ensure that LinearLayout
is specified in the Root element field, then click OK. The list_item.xml
file will appear in the layout
directory in the Project window and will open in the layout editor.
4. Select the LinearLayout
and change its orientation to horizontal
—this layout will consist of an ImageView
and a GridLayout
containing the other views.
Perform the following steps to add and configure the ImageView
:
1. Drag an ImageView
from the Palette onto the LinearLayout
in the Component Tree.
2. Set the id to conditionImageView
.
3. Set the layout:width to 50dp
—define the dimension resource image_side_length
for this value.
4. Set the layout:height to match_parent
—the ImageView
’s height will match the ListView
item’s height.
5. Set the contentDescription to the String
resource weather_condition_image
that you created in Section 7.4.3.
6. Set the scaleType to fitCenter
—the icon will fit within the ImageView
’s bounds and be centered horizontally and vertically.
Perform the following steps to add and configure the GridLayout
:
1. Drag a GridLayout
from the Palette onto the LinearLayout
in the Component Tree.
2. Set the columnCount to 3
and the rowCount to 2
.
3. Set the layout:width to 0dp
—this GridLayout
’s width will be determined by the layout:weight.
4. Set the layout:height to match_parent
—the GridLayout
’s height will match the ListView
item’s height.
5. Set the layout:weight to 1
—the GridLayout
’s width will occupy all remaining horizontal space in its parent LinearLayout
.
6. Check the useDefaultMargins property to add the default spacing between the GridLayout
’s cells.
Perform the following steps to add and configure the four TextView
s:
1. Drag a Large Text onto the GridLayout
in the Component Tree and set its id to dayTextView
, its layout:column to 0
and its layout:columnSpan to 3
.
2. Drag three Plain TextViews onto the GridLayout
in the Component Tree and set their ids to lowTextView
, hiTextView
and humidityTextView
, respectively. Set each of these TextView
s’ layout:row to 1
and layout:columnWeight to 1
. These TextView
s will all appear in the GridLayout
’s second row and, because they all have the same layout:columnWeight, the columns will be sized equally.
3. Set lowTextView
’s layout:column to 0
, hiTextView
’s layout:column to 1
and humidityTextView
’s layout:column to 2
.
This completes the list_item.xml
layout. You do not need to change the text property of any of the TextView
s—their text will be set programmatically.
This app consists of three classes that are discussed in Sections 7.5–7.7:
• Class Weather
(this section) represents one day’s weather data. Class MainActivity
will convert the JSON weather data into an ArrayList<Weather>
.
• Class WeatherArrayAdapter
(Section 7.6) defines a custom ArrayAdapter
subclass for binding the ArrayList<Weather>
to the MainActivity
’s ListView
. ListView
items are indexed from 0
and each ListView
item’s nested views are populated with data from the Weather
object at the same index in the ArrayList<Weather>
.
• Class MainActivity
(Section 7.7) defines the app’s user interface and the logic for interacting with the OpenWeatherMap.org
daily forecast web service and processing the JSON response.
In this section, we focus on class Weather
.
Figure 7.6 contains the package
statement, import
statements and class Weather
’s instance variables. You’ll use classes from the java.text
and java.util
packages (lines 5–8) to convert the timestamp for each day’s weather into that day’s name (Monday, Tuesday, etc.). The instance variables are declared final
, because they do not need to be modified after they’re initialized. We also made them public
—recall that Java String
s are immutable, so even though the instance variables are public
, their values cannot change.
1 // Weather.java
2 // Maintains one day's weather information
3 package com.deitel.weatherviewer;
4
5 import java.text.NumberFormat;
6 import java.text.SimpleDateFormat;
7 import java.util.Calendar;
8 import java.util.TimeZone;
9
10 class Weather {
11 public final String dayOfWeek;
12 public final String minTemp;
13 public final String maxTemp;
14 public final String humidity;
15 public final String description;
16 public final String iconURL;
17
The Weather
constructor (Fig. 7.7) initializes the class’s instance variables:
• The NumberFormat
object creates String
s from numeric values. Lines 22–23 configure the object to round floating-point values to whole numbers.
• Line 25 calls our utility method convertTimeStampToDay
(Section 7.5.3) to get the String
day name and initialize dayOfWeek
.
• Lines 26–27 format the day’s minimum and maximum temperature values as whole numbers using the numberFormat
object. We append °F to the end of each formatted String
, as we’ll request Fahrenheit
temperatures—the Unicode escape sequence u00B0
represents the degree symbol (°). The OpenWeatherMap.org
APIs also support Kelvin (the default) and Celsius temperature formats.
• Lines 28–29 get a NumberFormat
for locale-specific percentage formatting, then use it to format the humidity percentage. The web service returns this percentage as a whole number, so we divide that by 100.0 for formatting—in the U.S. locale, 1.00 is formatted as 100%, 0.5 is formatted as 50%, etc.
• Line 30 initializes the weather condition description.
• Lines 31–32 create a URL String
representing the weather condition image for the day’s weather—this will be used to download the image.
18 // constructor
19 public Weather(long timeStamp, double minTemp, double maxTemp,
20 double humidity, String description, String iconName) {
21 // NumberFormat to format double temperatures rounded to integers
22 NumberFormat numberFormat = NumberFormat.getInstance();
23 numberFormat.setMaximumFractionDigits(0);
24
25 this.dayOfWeek = convertTimeStampToDay(timeStamp);
26 this.minTemp = numberFormat.format(minTemp) + "u00B0F";
27 this.maxTemp = numberFormat.format(maxTemp) + "u00B0F";
28 this.humidity =
29 NumberFormat.getPercentInstance().format(humidity / 100.0);
30 this.description = description;
31 this.iconURL =
32 "http://openweathermap.org/img/w/" + iconName + ".png";
33 }
34
Utility method convertTimeStampToDay
(Fig. 7.8) receives as its argument a long value representing the number of seconds since January 1, 1970 GMT—the standard way time is represented on Linux systems (Android is based on Linux). To perform the conversion:
• Line 37 gets a Calendar
object for manipulating dates and times, then line 38 calls method setTimeInMillis
to set the time using the timestamp
argument. The timestamp
is in seconds so we multiply by 1000 to convert it to milliseconds.
• Line 39 gets the default TimeZone
object, which we use to adjust the time, based on the device’s time zone (lines 42–43).
• Line 46 creates a SimpleDateFormat
that formats a Date
object. The constructor argument "EEEE"
formats the Date
as just the day name (Monday, Tuesday, etc.). For a complete list of formats, visit:
• Line 47 formats and returns the day name. Calendar
’s getTime
method returns a Date
object containing the time. This Date
is passed to the SimpleDateFormat
’s format method to get the day name.
35 // convert timestamp to a day's name (e.g., Monday, Tuesday, ...)
36 private static String convertTimeStampToDay(long timeStamp) {
37 Calendar calendar = Calendar.getInstance(); // create Calendar
38 calendar.setTimeInMillis(timeStamp * 1000); // set time
39 TimeZone tz = TimeZone.getDefault(); // get device's time zone
40
41 // adjust time for device's time zone
42 calendar.add(Calendar.MILLISECOND,
43 tz.getOffset(calendar.getTimeInMillis()));
44
45 // SimpleDateFormat that returns the day's name
46 SimpleDateFormat dateFormatter = new SimpleDateFormat("EEEE");
47 return dateFormatter.format(calendar.getTime());
48 }
49 }
Class WeatherArrayAdapter
defines a subclass of ArrayAdapter
for binding an ArrayList<Weather>
to the MainActivity
’s ListView
.
Figure 7.9 contains WeatherArrayAdapter
’s package
statement and import
statements. We’ll discuss the imported types as we encounter them.
This app’s ListView
items require a custom layout. Each item contains an image (the weather-condition icon) and text representing the day, weather description, low temperature, high temperature and humidity. To map weather data to ListView
items, we extend class ArrayAdapter
(line 23) so that we can override ArrayAdapter
method getView
to configure a custom layout for each ListView
item.
1 // WeatherArrayAdapter.java
2 // An ArrayAdapter for displaying a List<Weather>'s elements in a ListView
3 package com.deitel.weatherviewer;
4
5 import android.content.Context;
6 import android.graphics.Bitmap;
7 import android.graphics.BitmapFactory;
8 import android.os.AsyncTask;
9 import android.view.LayoutInflater;
10 import android.view.View;
11 import android.view.ViewGroup;
12 import android.widget.ArrayAdapter;
13 import android.widget.ImageView;
14 import android.widget.TextView;
15
16 import java.io.InputStream;
17 import java.net.HttpURLConnection;
18 import java.net.URL;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22
23 class WeatherArrayAdapter extends ArrayAdapter<Weather> {
Nested class ViewHolder
(Fig. 7.10) defines instance variables that class WeatherArrayAdapter
accesses directly when manipulating ViewHolder
objects. When a ListView
item is created, we’ll associate a new ViewHolder
object with that item. If there’s an existing ListView
item that’s being reused, we’ll simply obtain that item’s ViewHolder
object.
24 // class for reusing views as list items scroll off and onto the screen
25 private static class ViewHolder {
26 ImageView conditionImageView;
27 TextView dayTextView;
28 TextView lowTextView;
29 TextView hiTextView;
30 TextView humidityTextView;
31 }
32
Figure 7.11 defines class WeatherArrayAdapter
’s instance variable and constructor. We use the instance variable bitmaps
(line 34)—a Map<String, Bitmap>
—to cache previously loaded weather-condition images, so they do not need to be re-downloaded as the user scrolls through the weather forecast. The cached images will remain in memory until Android terminates the app. The constructor (lines 37–39) simply calls the superclass’s three-argument constructor, passing the Context
(i.e., the activity in which the ListView
is displayed) and the List<Weather>
(the List
of data to display) as the first and third arguments. The second superclass constructor argument represents a layout resource ID for a layout that contains a TextView
in which a ListView
item’s data is displayed. The argument -1
indicates that we use a custom layout in this app, so we can display more than just one TextView
.
33 // stores already downloaded Bitmaps for reuse
34 private Map<String, Bitmap> bitmaps = new HashMap<>();
35
36 // constructor to initialize superclass inherited members
37 public WeatherArrayAdapter(Context context, List<Weather> forecast) {
38 super(context, -1, forecast);
39 }
40
Method getView (Fig. 7.12) is called to get the View
that displays a ListView
item’s data. Overriding this method enables you to map data to a custom ListView
item. The method receives the ListView
item’s position
, the View
(convertView
) representing that ListView
item and that ListView
item’s parent
as arguments. By manipulating convertView
, you can customize the ListView
item’s contents. Line 45 calls the inherited ArrayAdapter
method getItem
to get from the List<Weather>
the Weather
object that will be displayed. Line 47 defines the ViewHolder
variable that will be set to a new ViewHolder
object or an existing one, depending on whether method getView
’s convertView
argument is null
.
41 // creates the custom views for the ListView's items
42 @Override
43 public View getView(int position, View convertView, ViewGroup parent) {
44 // get Weather object for this specified ListView position
45 Weather day = getItem(position);
46
47 ViewHolder viewHolder; // object that reference's list item's views
48
49 // check for reusable ViewHolder from a ListView item that scrolled
50 // offscreen; otherwise, create a new ViewHolder
51 if (convertView == null) { // no reusable ViewHolder, so create one
52 viewHolder = new ViewHolder();
53 LayoutInflater inflater = LayoutInflater.from(getContext());
54 convertView =
55 inflater.inflate(R.layout.list_item, parent, false);
56 viewHolder.conditionImageView =
57 (ImageView) convertView.findViewById(R.id.conditionImageView);
58 viewHolder.dayTextView =
59 (TextView) convertView.findViewById(R.id.dayTextView);
60 viewHolder.lowTextView =
61 (TextView) convertView.findViewById(R.id.lowTextView);
62 viewHolder.hiTextView =
63 (TextView) convertView.findViewById(R.id.hiTextView);
64 viewHolder.humidityTextView =
65 (TextView) convertView.findViewById(R.id.humidityTextView);
66 convertView.setTag(viewHolder);
67 }
68 else { // reuse existing ViewHolder stored as the list item's tag
69 viewHolder = (ViewHolder) convertView.getTag();
70 }
71
72 // if weather condition icon already downloaded, use it;
73 // otherwise, download icon in a separate thread
74 if (bitmaps.containsKey(day.iconURL)) {
75 viewHolder.conditionImageView.setImageBitmap(
76 bitmaps.get(day.iconURL));
77 }
78 else {
79 // download and display weather condition image
80 new LoadImageTask(viewHolder.conditionImageView).execute(
81 day.iconURL);
82 }
83
84 // get other data from Weather object and place into views
85 Context context = getContext(); // for loading String resources
86 viewHolder.dayTextView.setText(context.getString(
87 R.string.day_description, day.dayOfWeek, day.description));
88 viewHolder.lowTextView.setText(
89 context.getString(R.string.low_temp, day.minTemp));
90 viewHolder.hiTextView.setText(
91 context.getString(R.string.high_temp, day.maxTemp));
92 viewHolder.humidityTextView.setText(
93 context.getString(R.string.humidity, day.humidity));
94
95 return convertView; // return completed list item to display
96 }
97
If convertView
is null
, line 52 creates a new ViewHolder
object to store references to a new ListView
item’s views. Next, line 53 gets the Context
’s LayoutInflator
, which we use in lines 54–55 to inflate the ListView
item’s layout. The first argument is the layout to inflate (R.layout.list_item
), the second is the layout’s parent ViewGroup
to which the layout’s views will be attached and the last argument is a boolean
indicating whether the views should be attached automatically. In this case, the third argument is false
, because the ListView
calls method getView
to obtain the item’s View
, then attaches it to the ListView
. Lines 56–65 get references to the views in the newly inflated layout and set the ViewHolder
’s instance variables. Line 66 sets the new ViewHolder
object as the ListView
item’s tag to store the ViewHolder
with the ListView
item for future use.
If convertView
is not null
, the ListView
is reusing a ListView
item that scrolled off the screen. In this case, line 69 gets the current ListView
item’s tag, which is the ViewHolder
that was previously attached to that ListView
item.
After creating or getting the ViewHolder
, lines 74–93 set the data for the ListItem
’s views. Lines 74–82 determine if the weather-condition image was previously downloaded, in which case the bitmaps
object will contain a key for the Weather
object’s iconURL
. If so, lines 75–76 get the existing Bitmap
from bitmaps
and set the conditionImageView
’s image. Otherwise, lines 80–81 create a new LoadImageTask
(Section 7.6.5) to download the image in a separate thread. The task’s execute method receives the iconURL
and initiates the task. Lines 86–93 set the String
s for the ListView
item’s TextView
s. Finally, line 95 returns the ListView
item’s configured View
.
Software Engineering Observation 7.1
Every time an AsyncTask
is required, you must create a new object of your AsyncTask
type—each AsyncTask
can be executed only once.
Nested class LoadImageTask
(Fig. 7.13) extends class AsyncTask
and defines how to download a weather-condition image in a separate thread, then return the image to the GUI thread for display in the ListView
item’s ImageView
.
98 // AsyncTask to load weather condition icons in a separate thread
99 private class LoadImageTask extends AsyncTask<String, Void, Bitmap> {
100 private ImageView imageView; // displays the thumbnail
101
102 // store ImageView on which to set the downloaded Bitmap
103 public LoadImageTask(ImageView imageView) {
104 this.imageView = imageView;
105 }
106
107 // load image; params[0] is the String URL representing the image
108 @Override
109 protected Bitmap doInBackground(String... params) {
110 Bitmap bitmap = null;
111 HttpURLConnection connection = null;
112
113 try {
114 URL url = new URL(params[0]); // create URL for image
115
116 // open an HttpURLConnection, get its InputStream
117 // and download the image
118 connection = (HttpURLConnection) url.openConnection();
119
120 try (InputStream inputStream = connection.getInputStream()) {
121 bitmap = BitmapFactory.decodeStream(inputStream);
122 bitmaps.put(params[0], bitmap); // cache for later use
123 }
124 catch (Exception e) {
125 e.printStackTrace();
126 }
127 }
128 catch (Exception e) {
129 e.printStackTrace();
130 }
131 finally {
132 connection.disconnect(); // close the HttpURLConnection
133 }
134
135 return bitmap;
136 }
137
138 // set weather condition image in list item
139 @Override
140 protected void onPostExecute(Bitmap bitmap) {
141 imageView.setImageBitmap(bitmap);
142 }
143 }
144 }
AsyncTask
is a generic type that requires three type parameters:
• The first is the variable-length parameter-list type (String
) for AsyncTask
’s doInBackground method, which you must overload (lines 108–136). When you call the task’s execute
method, it creates a thread in which doInBackground
performs the task. This app passes the weather-condition icon’s URL String
as the argument to the AsyncTask
’s execute
method (Fig. 7.12, lines 80–81).
• The second is the variable-length parameter-list type for the AsyncTask
’s onProgressUpdate method. This method executes in the GUI thread and is used to receive intermediate updates of the specified type from a long-running task. Overriding this method is optional. We don’t use it in this example, so we specify type Void
here and ignore this type parameter.
• The third is the type of the task’s result (Bitmap
), which is passed to AsyncTask
’s onPostExecute method (139–143). This method executes in the GUI thread and enables the ListView
item’s ImageView
to display the AsyncTask
’s results. The ImageView
to update is specified as an argument to class LoadImageTask
’s constructor (lines 103–105) and stored in the instance variable at line 100.
A key benefit of using an AsyncTask
is that it handles the details of creating threads and executing its methods on the appropriate threads for you, so that you do not have to interact with the threading mechanism directly.
Method doInBackground
uses an HttpURLConnection
to download the weather-condition image. Line 114 converts the URL String
that was passed to the AsyncTask
’s execute
method (params[0]
) into a URL
object. Next, line 118 calls class URL
’s method openConnection
to get an HttpURLConnection
—the cast is required, because the method returns a URLConnection
. Method openConnection
requests the content specified by URL
. Line 120 gets the HttpURLConnection
’s InputStream
, which we pass to BitmapFactory
method decodeStream
to read the image’s bytes and return a Bitmap
object containing the image (line 121). Line 122 caches the downloaded image in the bitmaps Map
for potential reuse and line 132 calls HttpURLConnection
’s inherited method disconnect
to close the connection and release its resources. Line 135 returns the downloaded Bitmap
, which is then passed to onPostExecute
—in the GUI thread—to display the image.
Class MainActivity
defines the app’s user interface, the logic for interacting with the OpenWeatherMap.org
daily forecast web service and the logic for processing the JSON response from the web service. The nested AsyncTask
subclass GetWeatherTask
performs the web service request in a separate thread (Section 7.7.5). MainActivity
does not require a menu in this app, so we removed the methods onCreateOptionsMenu
and onOptionsItemSelected
from the autogenerated code.
Figure 7.14 contains MainActivity
’s package
statement and import
statements. We’ll discuss the imported types as we encounter them.
1 // MainActivity.java
2 // Displays a 16-dayOfWeek weather forecast for the specified city
3 package com.deitel.weatherviewer;
4
5 import android.content.Context;
6 import android.os.AsyncTask;
7 import android.os.Bundle;
8 import android.support.design.widget.FloatingActionButton;
9 import android.support.design.widget.Snackbar;
10 import android.support.v7.app.AppCompatActivity;
11 import android.support.v7.widget.Toolbar;
12 import android.view.View;
13 import android.view.inputmethod.InputMethodManager;
14 import android.widget.EditText;
15 import android.widget.ListView;
16
17 import org.json.JSONArray;
18 import org.json.JSONException;
19 import org.json.JSONObject;
20
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStreamReader;
24 import java.net.HttpURLConnection;
25 import java.net.URL;
26 import java.net.URLEncoder;
27 import java.util.ArrayList;
28 import java.util.List;
29
Class MainActivity
(Fig. 7.15) extends class AppCompatActivity
and defines three instance variables:
• weatherList
(line 32) is an ArrayList<Weather>
that stores the Weather
objects—each represents one day in the daily forecast.
• weatherArrayAdapter
will refer to a WeatherArrayAdapter
object (Section 7.6) that binds the weatherList
to the ListView
’s items.
• weatherListView
will refer to MainActivity
’s ListView
.
30 public class MainActivity extends AppCompatActivity {
31 // List of Weather objects representing the forecast
32 private List<Weather> weatherList = new ArrayList<>();
33
34 // ArrayAdapter for binding Weather objects to a ListView
35 private WeatherArrayAdapter weatherArrayAdapter;
36 private ListView weatherListView; // displays weather info
37
Overridden method onCreate
(Fig. 7.15) configures MainActivity
’s GUI. Lines 41–45 were generated by Android Studio when you chose the Blank Activity template while creating this project. These lines inflate the GUI, create the app’s Toolbar
and attach the Toolbar
to the activity. Recall that an AppCompatActivity
must provide its own Toolbar
, because app bars (formerly called action bars) are not supported in early versions of Android.
Lines 48–50 configure the weatherListView
’s ListAdapter
—in this case, an object of the WeatherArrayAdapter
subclass of ArrayAdapter
. ListView
method setAdapter connects the WeatherArrayAdapter
to the ListView
for populating the ListView
’s items.
38 // configure Toolbar, ListView and FAB
39 @Override
40 protected void onCreate(Bundle savedInstanceState) {
41 super.onCreate(savedInstanceState);
42 // autogenerated code to inflate layout and configure Toolbar
43 setContentView(R.layout.activity_main);
44 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
45 setSupportActionBar(toolbar);
46
47 // create ArrayAdapter to bind weatherList to the weatherListView
48 weatherListView = (ListView) findViewById(R.id.weatherListView);
49 weatherArrayAdapter = new WeatherArrayAdapter(this, weatherList);
50 weatherListView.setAdapter(weatherArrayAdapter);
51
52 // configure FAB to hide keyboard and initiate web service request
53 FloatingActionButton fab =
54 (FloatingActionButton) findViewById(R.id.fab);
55 fab.setOnClickListener(new View.OnClickListener() {
56 @Override
57 public void onClick(View view) {
58 // get text from locationEditText and create web service URL
59 EditText locationEditText =
60 (EditText) findViewById(R.id.locationEditText);
61 URL url = createURL(locationEditText.getText().toString());
62
63 // hide keyboard and initiate a GetWeatherTask to download
64 // weather data from OpenWeatherMap.org in a separate thread
65 if (url != null) {
66 dismissKeyboard(locationEditText);
67 GetWeatherTask getLocalWeatherTask = new GetWeatherTask();
68 getLocalWeatherTask.execute(url);
69 }
70 else {
71 Snackbar.make(findViewById(R.id.coordinatorLayout),
72 R.string.invalid_url, Snackbar.LENGTH_LONG).show();
73 }
74 }
75 });
76 }
77
Lines 53–75 configure the FloatingActionButton
from the Blank Activity template. The onClick
listener method was autogenerated by Android Studio, but we reimplemented its body for this app. We get a reference to the app’s EditText
then use it in line 61 to get the user’s input. We pass that to method createURL
(Section 7.7.4) to create the URL representing the web service request that will return the city’s weather forecast.
If the URL is created successfully, line 66 programmatically hides the keyboard by calling method dismissKeyboard
(Section 7.7.4). Line 67 then creates a new GetWeatherTask
to obtain the weather forecast in a separate thread and line 68 executes the task, passing the URL of the web service request as an argument to AsyncTask
method execute
. If the URL is not created successfully, lines 71–72 create a Snackbar
indicating that the URL was invalid.
Figure 7.17 contains MainActivity
methods dismissKeyboard
and createURL
. Method dismissKeyboard
(lines 79–83) is called to hide the soft keyboard when the user touches the FloatingActionButtion
to submit a city to the app. Android provides a service for managing the keyboard programmatically. You can obtain a reference to this service (and many other Android services) by calling the inherited Context
method getSystemService with the appropriate constant—Context.INPUT_METHOD_SERVICE
in this case. This method can return objects of many different types, so you must cast its return value to the appropriate type—InputMethodManager (package android.view.inputmethod). To dismiss the keyboard, call InputMethodManager
method hideSoftInputFromWindow (line 82).
78 // programmatically dismiss keyboard when user touches FAB
79 private void dismissKeyboard(View view) {
80 InputMethodManager imm = (InputMethodManager) getSystemService(
81 Context.INPUT_METHOD_SERVICE);
82 imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
83 }
84
85 // create openweathermap.org web service URL using city
86 private URL createURL(String city) {
87 String apiKey = getString(R.string.api_key);
88 String baseUrl = getString(R.string.web_service_url);
89
90 try {
91 // create URL for specified city and imperial units (Fahrenheit)
92 String urlString = baseUrl + URLEncoder.encode(city, "UTF-8") +
93 "&units=imperial&cnt=16&APPID=" + apiKey;
94 return new URL(urlString);
95 }
96 catch (Exception e) {
97 e.printStackTrace();
98 }
99
100 return null; // URL was malformed
101 }
102
Method createURL
(lines 86–101) assembles the String
representation of the URL for the web service request (lines 92–93). Then line 94 attempts to create and return a URL
object initialized with the URL String
. In line 93, we add parameters to the web service query
&units=imperial&cnt=16&APPID=
The units
parameter can be imperial
(for Fahrenheit temperatures), metric
(for Celsius) or standard
(for Kelvin)—standard
is the default if you do not include the units parameter. The cnt
parameter specifies how many days should be included in the forecast. The maximum is 16 and the default is 7—providing an invalid number of days results in a seven-day forecast. Finally the APPID
parameter is for your OpenWeatherMap.org
API key, which we load into the app from the String
resource api_key
. By default, the forecast is returned in JSON format, but you can add the mode
parameter with the value XML
or HTML
, to receive XML formatted data or a web page, respectively.
Nested AsyncTask
subclass GetWeatherTask
(Fig. 7.18) performs the web service request and processes the response in a separate thread, then passes the forecast information as a JSONObject
to the GUI thread for display.
103 // makes the REST web service call to get weather data and
104 // saves the data to a local HTML file
105 private class GetWeatherTask
106 extends AsyncTask<URL, Void, JSONObject> {
107
108 @Override
109 protected JSONObject doInBackground(URL... params) {
110 HttpURLConnection connection = null;
111
112 try {
113 connection = (HttpURLConnection) params[0].openConnection();
114 int response = connection.getResponseCode();
115
116 if (response == HttpURLConnection.HTTP_OK) {
117 StringBuilder builder = new StringBuilder();
118
119 try (BufferedReader reader = new BufferedReader(
120 new InputStreamReader(connection.getInputStream()))) {
121
122 String line;
123
124 while ((line = reader.readLine()) != null) {
125 builder.append(line);
126 }
127 }
128 catch (IOException e) {
129 Snackbar.make(findViewById(R.id.coordinatorLayout),
130 R.string.read_error, Snackbar.LENGTH_LONG).show();
131 e.printStackTrace();
132 }
133
134 return new JSONObject(builder.toString());
135 }
136 else {
137 Snackbar.make(findViewById(R.id.coordinatorLayout),
138 R.string.connect_error, Snackbar.LENGTH_LONG).show();
139 }
140 }
141 catch (Exception e) {
142 Snackbar.make(findViewById(R.id.coordinatorLayout),
143 R.string.connect_error, Snackbar.LENGTH_LONG).show();
144 e.printStackTrace();
145 }
146 finally {
147 connection.disconnect(); // close the HttpURLConnection
148 }
149
150 return null;
151 }
152
153 // process JSON response and update ListView
154 @Override
155 protected void onPostExecute(JSONObject weather) {
156 convertJSONtoArrayList(weather); // repopulate weatherList
157 weatherArrayAdapter.notifyDataSetChanged(); // rebind to ListView
158 weatherListView.smoothScrollToPosition(0); // scroll to top
159 }
160 }
161
For class GetWeatherTask
the three generic type parameters are:
• URL
for the variable-length parameter-list type of AsyncTask
’s doInBackground
method (lines 108–51)—the URL of the web service request is passed as the only argument to the GetWeatherTask
’s execute
method.
• Void
for the variable-length parameter-list type for the onProgressUpdate
method—once again, we do not use this method.
• JSONObject
for the type of the task’s result, which is passed to onPostExecute
(154–159) in the GUI thread to display the results.
Line 113 in doInBackground
creates the HttpURLConnection
that’s used to invoke the REST web service. As in Section 7.6.5, simply opening the connection makes the request. Line 114 gets the response code from the web server. If the response code is HttpURLConnection.HTTP_OK
, the REST web service was invoked properly and there is a response to process. In this case, lines 119–126 get the HttpURLConnection
’s InputStream
, wrap it in a BufferedReader
, read each line of text from the response and append it to a StringBuilder
. Then, line 134 converts the JSON String
in the StringBuilder
to a JSONObject
and return it to the GUI thread. Line 147 disconnects the HttpURLConnection
.
If there’s an error reading the weather data or connecting to the web service, lines 129–130, 137–138 or 142–143 display a Snackbar
indicating the problem that occurred. These problems might occur if the device loses its network access in the middle of a request or if the device does not have network access in the first place—for example, if the device is in airplane mode.
When onPostExecute
is called in the GUI thread, line 156 calls method convertJSONtoArrayList
(Section 7.7.6) to extract the weather data from the JSONObject
and place it in the weatherList
. Then line 157 calls the ArrayAdapter
’s notifyDataSetChanged
method, which causes the weatherListView
to update itself with the new data. Line 158 calls ListView
method smoothScrollToPosition to reposition the ListView
’s first item to the top of the ListView
—this ensures that the new weather forecast’s first day is shown at the top.
In Section 7.3.2, we discussed the JSON returned by the OpenWeatherMap.org
daily weather forecast web service. Method convertJSONtoArrayList
(Fig. 7.19) extracts this weather data from its JSONObject
argument. First, line 164 clears the weatherList
of any existing Weather
objects. Processing JSON data in a JSONObject
or JSONArray
can result in JSONException
s, so lines 168–188 are placed in a try
block.
162 // create Weather objects from JSONObject containing the forecast
163 private void convertJSONtoArrayList(JSONObject forecast) {
164 weatherList.clear(); // clear old weather data
165
166 try {
167 // get forecast's "list" JSONArray
168 JSONArray list = forecast.getJSONArray("list");
169
170 // convert each element of list to a Weather object
171 for (int i = 0; i < list.length(); ++i) {
172 JSONObject day = list.getJSONObject(i); // get one day's data
173
174 // get the day's temperatures ("temp") JSONObject
175 JSONObject temperatures = day.getJSONObject("temp");
176
177 // get day's "weather" JSONObject for the description and icon
178 JSONObject weather =
179 day.getJSONArray("weather").getJSONObject(0);
180
181 // add new Weather object to weatherList
182 weatherList.add(new Weather(
183 day.getLong("dt"), // date/time timestamp
184 temperatures.getDouble("min"), // minimum temperature
185 temperatures.getDouble("max"), // maximum temperature
186 day.getDouble("humidity"), // percent humidity
187 weather.getString("description"), // weather conditions
188 weather.getString("icon"))); // icon name
189 }
190 }
191 catch (JSONException e) {
192 e.printStackTrace();
193 }
194 }
195 }
Line 168 obtains the "list" JSONArray
by calling JSONObject
method getJSONArray with the name of the array property as an argument. Next, lines 171–189 create a Weather
object for every element in the JSONArray
. JSONArray
method length returns the array’s number of elements (line 171).
Next, line 172 gets a JSONObject
representing one day’s forecast from the JSONArray
by calling method getJSONObject, which receives an index as its argument. Line 175 gets the "temp"
JSON object, which contains the day
’s temperature data. Lines 178–179 get the "weather"
JSON array, then get the array’s first element which contains the day
’s weather description and icon.
Lines 182–188 create a Weather
object and add it to the weatherList
. Line 183 uses JSONObject
method getLong to get the day
’s timestamp ("dt"
), which the Weather constructor converts to the day name. Lines 184–186 call JSONObject
method getDouble to get the minimum ("min"
) and maximum ("max"
) temperatures from the temperatures
object and the "humidity"
percentage from the day
object. Finally, lines 187–188 use getString to get the weather description and the weather-condition icon String
s from the weather
object.
In this chapter, you built the WeatherViewer app, which used OpenWeatherMap.org
web services to obtain a city’s 16-day weather forecast and display it in a ListView
. We discussed the architectural style for implementing web services known as REST (Representational State Transfer). You learned that apps use web standards, such as HyperText Transfer Protocol (HTTP), to invoke RESTful web services and receive their responses.
The OpenWeatherMap.org
web service used in this app returned the forecast as a String
in JavaScript Object Notation (JSON) format. You learned that JSON is a text-based format in which objects are represented as collections of name/value pairs. You used the classes JSONObject
and JSONArray
from the org.json
package to process the JSON data.
To invoke the web service, you converted the web service’s URL String
into a URL
object. You then used the URL
to open an HttpUrlConnection
that invoked the web service via an HTTP request. The app read all the data from the HttpUrlConnection
’s InputStream
and placed it in a String
, then converted that String
to a JSONObject
for processing. We demonstrated how to perform long-running operations outside the GUI thread and receive their results in the GUI thread by using AsyncTask
objects. This is particularly important for web-service requests, which have indeterminate response times.
You displayed the weather data in a ListView
, using a subclass of ArrayAdapter
to supply the data for each ListView
item. We showed how to improve a ListView
’s performance via the view-holder pattern by reusing existing ListView
items’ views as the items scroll off the screen.
Finally, you used several material-design features from the Android Design Support Library’s—a TextInputLayout
to keep an EditText
’s hint on the screen even after the user began entering text, a FloatingActionButton
to enable the user to submit input and a Snackbar
to display an informational message to the user.
In Chapter 8, we build the Twitter® Searches app. Many mobile apps display lists of items, just as we did in this app. In Chapter 8, you’ll do this by using a RecyclerView
that obtains data from an ArrayList<String>
. For large data sets, RecyclerView
is more efficient than ListView
. You’ll also store app data as user preferences and learn how to launch the device’s web browser to display a web page.
7.1 Fill in the blanks in each of the following statements:
a) A(n) ____________ is a software component that can be accessed over a network.
b) ____________ refers to an architectural style for implementing web services—often called RESTful web services
c) Classes from the ____________ package process JSON data.
d) To invoke a REST web service, you can use a URL
to open a(n) ____________, which makes the HTTP request to the web service.
e) A(n) ____________ (package android.widget) displays a scrollable list of items.
f) A(n) ____________ is a button that floats over the user interface.
7.2 State whether each of the following is true or false. If false, explain why.
a) Many of today’s most popular free and fee-based web services are RESTful.
b) REST is a standard protocol.
c) You should perform long-running operations or operations that block execution until they complete (e.g., network, file and database access) outside the GUI thread.
d) JSON (JavaScript Object Notation)—a simple way to represent JavaScript objects as numbers—is an alternative to XML for passing data between the client and the server.
e) Arrays are represented in JSON with curly braces in the following format:
{
value1,
value2,
value3 }
f) Class JSONObject
’s get methods enable you to obtain a JSON key’s value as one of the types JSONObject
, JSONArray
, Object
, boolean
, double
, int
, long
or String
.
a) web service.
b) Representational State Transfer (REST).
c) org.json
.
d) HttpUrlConnection
.
e) ListView
.
f) FloatingActionButton
.
a) True.
b) False. Though REST itself is not a standard, RESTful web services use web standards, such as HyperText Transfer Protocol (HTTP), which is used by web browsers to communicate with web servers.
c) True.
d) False. JSON (JavaScript Object Notation)—a simple way to represent JavaScript objects as strings—is an alternative to XML for passing data between the client and the server.
e) False. Arrays are represented in JSON with square brackets.
f) True.
a) The machine on which a web service resides is the ____________.
b) Class ____________ provides methods for accessing the elements of a JSON array.
c) To perform long-running tasks that result in updates to the GUI, Android provides class____________ (package android.os
), which performs the long-running operation in one thread and delivers the results to the GUI thread.
d) In the ____________ you create a class containing instance variables for the views that display a ListView
item’s data. When a ListView
item is created, you also create an object of this class and initialize its instance variables with references to the item’s nested views, then store that object with the ListView item.
e) Class JSONObject
’s ____________ method returns the String
for a given key.
f) ListView
is a subclass of ____________, which represents a view that gets its data from a data source via an Adapter
object.
7.4 State whether each of the following is true or false. If false, explain why.
a) To receive a JSON response from a web service invoked by an HttpUrlConnection
, you read from the connection’s InputStream
.
b) Each object in JSON is represented as a list of property names and values contained in curly braces, in the following format:
{
"propertyName1" :
value1,
"propertyName2'":
value2 }
c) Each value in a JSON array can be a string, a number, a JSON representation of an object, true
, false
or null
.
d) A ListAdapter
populates a ListView
using data from an ArrayList
collection object.
e) In an EditTextLayout
, when the EditText
receives the focus, the layout animates the hint text from it’s original size to a smaller size that’s displayed above the EditText
.
7.5 (Enhanced Weather Viewer App) Investigate Android’s capabilities for getting a device’s last known location
and the Android class Geocoder
When the app first loads, use these capabilities to prepopulate the app’s EditText
with the user’s location and to display the weather for that location.
7.6 (Enhanced Quiz App) Modify the Flag Quiz app in Chapter 4 to create your own quiz app that shows videos rather than images. Possible quizzes could include U.S. presidents, world landmarks, movie stars, recording artists, and more. Consider using YouTube web services (https://developers.google.com/youtube/
) to obtain videos for display in the app. (Be sure to read the YouTube API terms of service at https://developers.google.com/youtube/terms
.)
7.7 (Word Scramble Game) Create an app that scrambles the letters of a word or phrase and asks the user to enter the correct word or phrase. Keep track of the user’s high score in the app’s SharedPreferences
. Include levels (three-, four-, five-, six- and seven-letter words). As a hint to the user, provide a definition with each word. Use an online dictionary’s web services to select the words and the definitions that are used for hints.
7.8 (Crossword Puzzle Generator App) Most people have worked a crossword puzzle, but few have ever attempted to generate one. Create a crossword generator app that use an online dictionary’s web services to select the words and the definitions that are used for hints. Display the corresponding hints when the user touches the first square in a word. If the square represents the beginning of both a horizontal and vertical word, show both hints.
Web services, inexpensive computers, abundant high-speed Internet access, open source software and many other elements have inspired new, exciting, lightweight business models that people can launch with only a small investment. Some types of websites with rich and robust functionality that might have required hundreds of thousands or even millions of dollars to build in the 1990s can now be built for nominal sums. In Chapter 1, we introduced the application-development methodology of mashups, in which you can rapidly develop powerful and intriguing applications by combining (often free) complementary web services and other forms of information feeds. One of the first mashups was www.housingmaps.com
, which combined the real estate listings provided by www.craigslist.org
with the mapping capabilities of Google Maps—the most widely-used web-service API—to offer maps that showed the locations of apartments for rent in a given area. Figure 1.4 provided a list of several popular web services available from companies including Google, Facebook, eBay, Netflix, Skype and more.
Check out the catalog of web-service APIs at www.programmableweb.com
and the apps in Android Market for inspiration. It’s important to read the terms of service for the APIs before building your apps. Some APIs are free while others may charge fees. There also may be restrictions on the frequency with which your app may query the server.
7.9 (Mashup) Use your imagination to create a mashup app using at least two APIs of your choice.
7.10 (News Aggregator App) Use web services to create a news aggregator app that gathers news from multiple sources.
7.11 (Enhanced News Aggregator App) Enhance the News Aggregator app using a maps API. Allow the user to select a region of the world. When the user clicks on a region, display the headlines from the multiple news sources.
7.12 (Shopping Mashup App) Create a location-based shopping app using APIs from CityGrid® (www.citygridmedia.com/developer/
) or a similar shopping service. Add background music to your app using APIs from a service such as Last.fm (www.last.fm/api
) so the user can listen while shopping.
7.13 (Daily Deals Mashup App) Create a local daily deals app using Groupon APIs (www.groupon.com/pages/api
) or those of a similar service.
7.14 (Idiomatic Expressions Translator Mashup App) An idiomatic expression is a common, often strange saying whose meaning cannot be understood from the words in the expression. For example, you might say your favorite sports team is going to “eat [their opponent] for lunch,” or “blow [their opponent] out of the water” to indicate that you predict your team will win decisively. Search the web to find popular idiomatic expressions. Create an app that allows the user to enter an idiomatic expression by text or speech, then translate the expression into a foreign language and then back to English. Use a translation API (such as Bing) to perform the translation. Allow the user to select the foreign language. Display the results in English—they may be funny or interesting.
7.15 (Name That Song App) Check your favorite music sites to see if they have a web services API. Using a music web services API, create a quiz app (similar to the Flag Quiz app in Chapter 5) that plays a song and asks the user to name the song. Other features to include:
a) Add three lifelines that allow you to call one contact, SMS one contact and e-mail one contact for help answering a question. Once each lifeline is used, disable the capability for that quiz.
b) Add a timer function so that the user must answer each question within 10 seconds.
c) Add multiplayer functionality that allows two users to play on the same device.
d) Add muliplayer functionality to allow users on different devices to compete in the same game.
e) Keep track of the user’s score and display it as a percentage at the bottom of the screen throughout the quiz.