253 253 Chapter Communicating via the Internet The expectation is that most, if not all, Android devices will have built-in Internet access. That could be Wi-Fi, cellular data services (EDGE, 3G, etc.), or possibly something else entirely. Regardless, most people—or at least those with a data plan or Wi-Fi access— will be able to get to the Internet from their Android phone. Not surprisingly, the Android platform gives developers a wide range of ways to make use of this Internet access. Some offer high-level access, such as the integrated WebKit browser component. If you want, you can drop all the way down to using raw sockets. In between, you can leverage APIs—both on-device and from third-party JARs—that give you access to specific protocols: HTTP, XMPP, SMTP, and so on. The emphasis of this book is on the higher-level forms of access: the WebKit component (discussed in Chapter 13) and Internet-access APIs (discussed in this chapter). As busy coders, we should be trying to reuse existing components versus rolling our own on-the-wire protocol wherever possible. REST and Relaxation Android does not have built-in SOAP or XML-RPC client APIs. However, it does have the Apache HttpComponents library baked in. You can either layer a SOAP/XML-RPC layer atop this library or use it “straight” for accessing REST-style web services. For the purposes of this book, REST-style web services are considered simple HTTP requests for ordinary URLs over the full range of HTTP verbs, with formatted payloads (XML, JSON, etc.) as responses. More expansive tutorials, FAQs, and HOWTOs can be found at the HttpClient web site (http://hc.apache.org/). Here, we’ll cover the basics, while checking the weather. 25 CHAPTER 25: Communicating via the Internet 254 HTTP Operations via Apache HttpClient The HttpClient component of HttpComponents handles all HTTP requests on your behalf. The first step to using HttpClient is, not surprisingly, to create an HttpClient object. Since HttpClient is an interface, you will need to actually instantiate some implementation of that interface, such as DefaultHttpClient. Those requests are bundled up into HttpRequest instances, with different HttpRequest implementations for each different HTTP verb (e.g., HttpGet for HTTP GET requests). You create an HttpRequest implementation instance, fill in the URL to retrieve and other configuration data (e.g., form values if you are doing an HTTP POST via HttpPost), and then pass the method to the client to actually make the HTTP request via execute(). What happens at this point can be as simple or as complicated as you want. You can get an HttpResponse object back, with a response code (e.g., 200 for OK), HTTP headers, and the like. Or, you can use a flavor of execute() that takes a ResponseHandler<String> as a parameter, with the net result being that execute() returns just the String representation of the response body. In practice, this is not a recommended approach, because you really should be checking your HTTP response codes for errors. However, for trivial applications, like book examples, the ResponseHandler<String> approach works just fine. For example, let’s take a look at the Internet/Weather sample project. This implements an activity that retrieves weather data for your current location from the National Weather Service. (Note that this probably only works only for geographic locations in the United States.) That data is converted into an HTML page, which is poured into a WebKit widget for display. Rebuilding this demo using a ListView is left as an exercise for the reader. Also, since this sample is relatively long, we will only show relevant pieces of the Java code here in this chapter, though you can always download the full source code from the Apress web site. To make this a bit more interesting, we use the Android location services to figure out where we are—well, sort of. The full details of how that works are left until Chapter 32. In the onResume() method, we toggle on location updates, so we will be informed where we are now and when we move a significant distance (10 kilometers). When a location is available—either at the start or based on movement—we retrieve the National Weather Service data via our updateForecast() method: private void updateForecast(Location loc) { String url=String.format(format, loc.getLatitude(), loc.getLongitude()); HttpGet getMethod=new HttpGet(url); try { ResponseHandler<String> responseHandler=new BasicResponseHandler(); String responseBody=client.execute(getMethod, responseHandler); buildForecasts(responseBody); CHAPTER 25: Communicating via the Internet 255 String page=generatePage(); browser.loadDataWithBaseURL(null, page, "text/html", "UTF-8", null); } catch (Throwable t) { Toast .makeText(this, "Request failed: "+t.toString(), 4000) .show(); } } The updateForecast() method takes a Location as a parameter, obtained from the location update process. For now, all you need to know is that Location sports getLatitude() and getLongitude() methods, which return the latitude and longitude of the device’s position, respectively. We hold the URL to the National Weather Service XML in a string resource, and pour in the latitude and longitude at runtime. Given our HttpClient object created in onCreate(), we populate an HttpGet with that customized URL, and then execute that method. Given the resulting XML from the REST service, we build the forecast HTML page, as described next, and pour that into the WebKit widget. If the HttpClient blows up with an exception, we provide that error as a Toast. Parsing Responses The response you get will be formatted using some system—HTML, XML, JSON, or whatever. It is up to you, of course, to pick out the information you need and do something useful with it. In the case of the WeatherDemo, we need to extract the forecast time, temperature, and icon (indicating sky conditions and precipitation), and generate an HTML page from it. Android includes three XML parsers: the traditional W3C DOM parser (org.w3c.dom), a SAX parser (org.xml.sax), and the XML pull parser (discussed in Chapter 20). It also has a JSON parser (org.json). You are also welcome to use third-party Java code, where possible, to handle other formats, such as a dedicated RSS/Atom parser for a feed reader. The use of third-party Java code is discussed in Chapter 24. For WeatherDemo, we use the W3C DOM parser in our buildForecasts() method: void buildForecasts(String raw) throws Exception { DocumentBuilder builder=DocumentBuilderFactory .newInstance() .newDocumentBuilder(); Document doc=builder.parse(new InputSource(new StringReader(raw))); NodeList times=doc.getElementsByTagName("start-valid-time"); for (int i=0;i<times.getLength();i++) { Element time=(Element)times.item(i); Forecast forecast=new Forecast(); CHAPTER 25: Communicating via the Internet 256 forecasts.add(forecast); forecast.setTime(time.getFirstChild().getNodeValue()); } NodeList temps=doc.getElementsByTagName("value"); for (int i=0;i<temps.getLength();i++) { Element temp=(Element)temps.item(i); Forecast forecast=forecasts.get(i); forecast.setTemp(new Integer(temp.getFirstChild().getNodeValue())); } NodeList icons=doc.getElementsByTagName("icon-link"); for (int i=0;i<icons.getLength();i++) { Element icon=(Element)icons.item(i); Forecast forecast=forecasts.get(i); forecast.setIcon(icon.getFirstChild().getNodeValue()); } } The National Weather Service XML format is curiously structured, relying heavily on sequential position in lists versus the more object-oriented style you find in formats like RSS or Atom. That being said, we can take a few liberties and simplify the parsing somewhat, taking advantage of the fact that the elements we want (start-valid-time for the forecast time, value for the temperature, and icon-link for the icon URL) are unique within the document. The HTML comes in as an InputStream and is fed into the DOM parser. From there, we scan for the start-valid-time elements and populate a set of Forecast models using those start times. Then we find the temperature value elements and icon-link URLs and fill those in to the Forecast objects. In turn, the generatePage() method creates a rudimentary HTML table with the forecasts: String generatePage() { StringBuffer bufResult=new StringBuffer("<html><body><table>"); bufResult.append("<tr><th width=\"50%\">Time</th>"+ "<th>Temperature</th><th>Forecast</th></tr>"); for (Forecast forecast : forecasts) { bufResult.append("<tr><td align=\"center\">"); bufResult.append(forecast.getTime()); bufResult.append("</td><td align=\"center\">"); bufResult.append(forecast.getTemp()); bufResult.append("</td><td><img src=\""); bufResult.append(forecast.getIcon()); bufResult.append("\"></td></tr>"); } CHAPTER 25: Communicating via the Internet 257 bufResult.append("</table></body></html>"); return(bufResult.toString()); } The result looks like Figure 25–1. Figure 25–1. The WeatherDemo sample application Stuff to Consider If you need to use Secure Sockets Layer (SSL) protocol, bear in mind that the default HttpClient setup does not include SSL support. Mostly, this is because you need to decide how to handle SSL certificate presentation. Do you blindly accept all certificates, even self-signed or expired ones? Or do you want to ask the users if they really want to use some strange certificates? Similarly, the HttpClient component, by default, is designed for single-threaded use. If you will be using HttpClient from a service or some other place where multiple threads might be an issue, you can readily set up HttpClient to support multiple threads. For these sorts of topics, you are best served by checking out the HttpClient web site for documentation and support. CHAPTER 25: Communicating via the Internet 258 . a data plan or Wi-Fi access— will be able to get to the Internet from their Android phone. Not surprisingly, the Android platform gives developers a wide range of ways to make use of this Internet. are also welcome to use third-party Java code, where possible, to handle other formats, such as a dedicated RSS/Atom parser for a feed reader. The use of third-party Java code is discussed. way down to using raw sockets. In between, you can leverage APIs—both on-device and from third-party JARs—that give you access to specific protocols: HTTP, XMPP, SMTP, and so on. The emphasis