7. WeatherViewer App


Objectives

In this chapter you’ll:

Image Use the free OpenWeatherMap.org REST web services to get a 16-day weather forecast for a city specified by the user.

Image 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.

Image Process a JSON response using package org.json classes JSONObjects and JSONArrays.

Image Define an ArrayAdapter that specifies the data to display in a ListView.

Image Use the ViewHolder pattern to reuse views that scroll off the screen in a ListView, rather than creating new views.

Image Use the material design components TextInputLayout, Snackbar and FloatingActionButton from the Android Design Support Library.


7.1 Introduction

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:

Image

Fig. 7.1 | Weather Viewer app displaying the New York, NY, US weather forecast.

7.2 Test-Driving the WeatherViewer App

Opening and Running the App

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.

Viewing a City’s 16-Day Weather Forecast

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 (Image) to submit the city to the app, which then requests that city’s 16-day weather forecast (shown in Fig. 7.1).

Image

Fig. 7.2 | Entering a city.

7.3 Technologies Overview

This section introduces the features you’ll use to build the WeatherViewer app.

7.3.1 Web Services

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.

REST Web Services

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.

Web Services Often Require an API Key

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.

OpenWeatherMap.org Web Services

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 Web Service License

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

Obtaining an OpenWeatherMap.org API Key

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.

7.3.2 JavaScript Object Notation (JSON) and the org.json Package

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 Strings. 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   }


Fig. 7.3 | Sample JSON from the OpenWeatherMap.org daily forecast web service.

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").

org.json Package

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.

7.3.3 HttpUrlConnection Invoking a REST Web Service

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.

7.3.4 Using AsyncTask to Perform Network Requests Outside the GUI Thread

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).

7.3.5 ListView, ArrayAdapter and the View-Holder Pattern

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 AdapterViews 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

View-Holder Pattern

By default, a ListView can display one or two TextViews. In this app, you’ll customize the ListView items to display an ImageView and several TextViews 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.

7.3.6 FloatingActionButton

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 (Image) 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:

7.3.7 TextInputLayout

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.

7.3.8 Snackbar

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, Snackbars 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.

7.4 Building the App’s GUI and Resource Files

In this section, we review the new features in the GUI and resource files for the Weather Viewer app.

7.4.1 Creating the Project

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.

7.4.2 AndroidManifest.xml

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.

Permissions That Are Automatically Granted in Android 6.0

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.

7.4.3 strings.xml

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.

Image

Fig. 7.4 | String resources used in the WeatherViewer app.

7.4.4 colors.xml

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.

7.4.5 activity_main.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 (Image) 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.

7.4.6 content_main.xml

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.

7.4.7 list_item.xml

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).

Image

Fig. 7.5 | Layout for one day’s weather displayed in a ListView item.

Step 1: Creating the Layout File and Customizing the LinearLayout’s Orientation

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.

Step 2: Adding the ImageView for Displaying a Weather-Condition Icon

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.

Step 3: Adding the GridLayout for Displaying the TextViews

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.

Step 4: Adding the TextViews

Perform the following steps to add and configure the four TextViews:

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 TextViews’ layout:row to 1 and layout:columnWeight to 1. These TextViews 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 TextViews—their text will be set programmatically.

7.5 Class Weather

This app consists of three classes that are discussed in Sections 7.57.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.

7.5.1 package Statement, import Statements and Instance Variables

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 Strings 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


Fig. 7.6 | Weather class package statement, import statements and instance variables.

7.5.2 Constructor

The Weather constructor (Fig. 7.7) initializes the class’s instance variables:

• The NumberFormat object creates Strings 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


Fig. 7.7 | Weather class constructor.

7.5.3 Method convertTimeStampToDay

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   }


Fig. 7.8 | Weather method convertTimeStampToDay.

7.6 Class WeatherArrayAdapter

Class WeatherArrayAdapter defines a subclass of ArrayAdapter for binding an ArrayList<Weather> to the MainActivity’s ListView.

7.6.1 package Statement and import Statements

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> {


Fig. 7.9 | WeatherArrayAdapter class package statement and import statements.

7.6.2 Nested Class ViewHolder

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


Fig. 7.10 | Nested class ViewHolder.

7.6.3 Instance Variable and Constructor

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


Fig. 7.11 | WeatherArrayAdapter class instance variable and constructor.

7.6.4 Overridden ArrayAdapter Method getView

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


Fig. 7.12 | Overridden ArrayAdapter method getView.

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 Strings for the ListView item’s TextViews. Finally, line 95 returns the ListView item’s configured View.


Image 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.


7.6.5 AsyncTask Subclass for Downloading Images in a Separate Thread

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   }


Fig. 7.13 | AsyncTask subclass for downloading images in a separate thread.

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.

Downloading the Weather-Condition Image

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.

7.7 Class MainActivity

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.

7.7.1 package Statement and import Statements

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


Fig. 7.14 | Class MainActivity’s package statement and import statements.

7.7.2 Instance Variables

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


Fig. 7.15 | Class MainActivity’s instance variables.

7.7.3 Overridden Activity Method onCreate

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


Fig. 7.16 | Overridden Activity method onCreate.

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.

7.7.4 Methods dismissKeyboard and createURL

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


Fig. 7.17 | MainActivity methods dismissKeyboard and createURL.

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.

7.7.5 AsyncTask Subclass for Invoking a Web Service

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


Fig. 7.18 | AsyncTask subclass for invoking a web service.

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.

7.7.6 Method convertJSONtoArrayList

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 JSONExceptions, 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   }


Fig. 7.19 | MainActivity method convertJSONtoArrayList.

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 Strings from the weather object.

7.8 Wrap-Up

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.

Self-Review Exercises

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.

Answers to Self-Review Exercises

7.1

a) web service.

b) Representational State Transfer (REST).

c) org.json.

d) HttpUrlConnection.

e) ListView.

f) FloatingActionButton.

7.2

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.

Exercises

7.3 Fill in the blanks in each of the following statements:

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 and Mashups

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.

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

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