|
Cyril Sermon (@admin) |
Creating an Earthquake Viewer
In the following example, you’ll create a tool that uses a USGS earthquake feed to display a list of recent earthquakes.
You will return to this Earthquake application several times, first in Chapter 6 to save and share the earthquake data with a Content Provider, and again in Chapters 7 and 8 to add mapping support and to move the earthquake updates into a background Service.
In this example, you will create a list-based Activity that connects to an earthquake feed and displays the location, magnitude, and time of the earthquakes it contains. You’ll use an Alert Dialog to provide a detail window that includes a linkified Text View with a link to the USGS web site.
Start by creating an Earthquake project featuring an Earthquake Activity. Modify the main.xml layout resource to include a List View control — be sure to name it so you can reference it from the Activity code.
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”>
<ListView
android:id=”@+id/earthquakeListView”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
/>
</LinearLayout>
Create a new public Quake class. This class will be used to store the details (date, details, loca-tion, magnitude, and link) of each earthquake. Override the toString method to provide the string that will be used for each quake in the List View.
package com.paad.earthquake;
import java.util.Date;
import java.text.SimpleDateFormat;
import android.location.Location;
public class Quake {
private Date date;
private String details;
private Location location;
private double magnitude;
private String link;
public Date getDate() { return date; }
public String getDetails() { return details; } public Location getLocation() { return location; } public double getMagnitude() { return magnitude; } public String getLink() { return link; }
public Quake(Date _d, String _det, Location _loc, double _mag, String _link) {
date = _d;
details = _det;
location = _loc;
magnitude = _mag;
link = _link;
}
@Override
public String toString() {
SimpleDateFormat sdf = new SimpleDateFormat(“HH.mm”); String dateString = sdf.format(date);
return dateString + “: “ + magnitude + “ “ + details;
}
}
In the Earthquake Activity, override the onCreate method to store an ArrayList of Quake objects, and bind that to the ListView using an ArrayAdapter.
package com.paad.earthquake;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import android.app.Activity;
import android.app.Dialog;
import android.location.Location;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.WindowManager;
import android.view.MenuItem;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
public class Earthquake extends Activity {
ListView earthquakeListView;
ArrayAdapter<Quake> aa;
ArrayList<Quake> earthquakes = new ArrayList<Quake>();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
earthquakeListView =
(ListView)this.findViewById(R.id.earthquakeListView);
int layoutID = android.R.layout.simple_list_item_1;
= new ArrayAdapter<Quake>(this, layoutID , earthquakes); earthquakeListView.setAdapter(aa);
}
}
Next, you should start processing the earthquake feed. For this example, the feed used is the 1-day USGS feed for earthquakes with a magnitude greater than 2.5.
Add the location of your feed as an external string resource. This lets you potentially specify a different feed based on a user’s location.
<?xml version=”1.0” encoding=”utf-8”?>
<resources>
<string name=”app_name”>Earthquake</string>
<string name=”quake_feed”>
http://earthquake.usgs.gov/eqcenter/catalogs/1day-M2.5.xml </string>
</resources>
Before your application can access the Internet, it needs to be granted permission for Internet access. Add the uses-permission to the manifest.
<uses-permission xmlns:android=”http://schemas.android.com/apk/res/android” android:name=”android.permission.INTERNET”>
</uses-permission>
Returning to the Earthquake Activity, create a new refreshEarthquakes method that con-nects to, and parses, the earthquake feed. Extract each earthquake, and parse the details to obtain the date, magnitude, link, and location. As you finish parsing each earthquake, pass it in to a new addNewQuake method.
The XML parsing is presented here without further comment.
private void refreshEarthquakes() {
Get the XML URL url;
try {
String quakeFeed = getString(R.string.quake_feed); url = new URL(quakeFeed);
URLConnection connection;
connection = url.openConnection();
HttpURLConnection httpConnection = (HttpURLConnection)connection; int responseCode = httpConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) { InputStream in = httpConnection.getInputStream();
DocumentBuilderFactory dbf;
dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder();
Parse the earthquake feed. Document dom = db.parse(in);
Element docEle = dom.getDocumentElement();
Clear the old earthquakes earthquakes.clear();
Get a list of each earthquake entry.
NodeList nl = docEle.getElementsByTagName(“entry”); if (nl != null && nl.getLength() > 0) {
for (int i = 0 ; i < nl.getLength(); i++) {
Element entry = (Element)nl.item(i);
Element title =
(Element)entry.getElementsByTagName(“title”).item(0);
Element g =
(Element)entry.getElementsByTagName(“georss:point”).item(0);
Element when =
(Element)entry.getElementsByTagName(“updated”).item(0);
Element link =
(Element)entry.getElementsByTagName(“link”).item(0);
String details = title.getFirstChild().getNodeValue();
String hostname = “http://earthquake.usgs.gov”;
String linkString = hostname + link.getAttribute(“href”);
String point = g.getFirstChild().getNodeValue();
String dt = when.getFirstChild().getNodeValue();
SimpleDateFormat sdf;
sdf = new SimpleDateFormat(“yyyy-MM-dd’T’hh:mm:ss’Z’”); Date qdate = new GregorianCalendar(0,0,0).getTime(); try {
qdate = sdf.parse(dt);
} catch (ParseException e) { e.printStackTrace();
}
String[] location = point.split(“ “); Location l = new Location(“dummyGPS”); l.setLatitude(Double.parseDouble(location[0])); l.setLongitude(Double.parseDouble(location[1]));
String magnitudeString = details.split(“ “)[1];
int end = magnitudeString.length()-1;
double magnitude;
magnitude = Double.parseDouble(magnitudeString.substring(0, end));
details = details.split(“,”)[1].trim();
Quake quake = new Quake(qdate, details, l, magnitude, linkString);
Process a newly found earthquake addNewQuake(quake);
}
}
}
} catch (MalformedURLException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace();
} catch (ParserConfigurationException e) { e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
finally {
}
}
private void addNewQuake(Quake _quake) {
// TODO: Add the earthquakes to the array list.
}
Update the addNewQuake method so that it takes each newly processed quake and adds it to the Earthquake ArrayList. It should also notify the Array Adapter that the underlying data have changed.
private void addNewQuake(Quake _quake) {
Add the new quake to our list of earthquakes. earthquakes.add(_quake);
Notify the array adapter of a change. aa.notifyDataSetChanged();
}
Modify your onCreate method to call refreshEarthquakes on start-up.
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
earthquakeListView =
(ListView)this.findViewById(R.id.earthquakeListView);
int layoutID = android.R.layout.simple_list_item_1;
= new ArrayAdapter<Quake>(this, layoutID , earthquakes); earthquakeListView.setAdapter(aa);
refreshEarthquakes();
}
The Internet lookup is currently happening on the main UI thread. This is bad form as the application will become unresponsive if the lookup takes longer than a few seconds. In Chapter 8, you’ll learn how to move expensive or time-consuming operations like this onto the background thread.
If you run your project, you should see a List View that features the earthquakes from the last 24 hours with a magnitude greater than 2.5, as shown in the screenshot in Figure 5-6.
There are only two more steps to make this a more useful application. First, create a new menu item to let users refresh the earthquake feed on demand.
10.1. Start by adding a new external string for the menu option.
<string name=”menu_update”>Refresh Earthquakes</string>
10.2 Then override the Activity’s onCreateOptionsMenu and onOptionsItemSelected methods to display and handle the refresh earthquakes menu item.
static final private int MENU_UPDATE = Menu.FIRST;
@Override
public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu);
menu.add(0, MENU_UPDATE, Menu.NONE, R.string.menu_update);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case (MENU_UPDATE): {
refreshEarthquakes();
return true;
}
}
return false;
}
Now add some interaction. Let users find more details by opening a Dialog box when they select an earthquake from the list.
11.1. Creating a new quake_details.xml layout resource for the Dialog box you’ll display on an item click.
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:padding=”10sp”>
<TextView
android:id=”@+id/quakeDetailsTextView”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:textSize=”14sp”
/>
</LinearLayout>
11.2. Then modify your onCreate method to add an ItemClickListener to the List View that displays a Dialog box whenever an earthquake item is selected.
static final private int QUAKE_DIALOG = 1;
Quake selectedQuake;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
earthquakeListView =(ListView)this.findViewById(R.id.earthquakeListView);
earthquakeListView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView _av, View _v, int _index,
long arg3) {
selectedQuake = earthquakes.get(_index);
showDialog(QUAKE_DIALOG);
}
});
int layoutID = android.R.layout.simple_list_item_1;
= new ArrayAdapter<Quake>(this, layoutID , earthquakes); earthquakeListView.setAdapter(aa);
refreshEarthquakes();
}
11.3. Now override the onCreateDialog and onPrepareDialog methods to create and populate the Earthquake Details dialog.
@Override
public Dialog onCreateDialog(int id) {
switch(id) {
case (QUAKE_DIALOG) :
LayoutInflater li = LayoutInflater.from(this);
View quakeDetailsView = li.inflate(R.layout.quake_details, null);
AlertDialog.Builder quakeDialog = new AlertDialog.Builder(this); quakeDialog.setTitle(“Quake Time”); quakeDialog.setView(quakeDetailsView); return quakeDialog.create();
}
return null;
}
@Override
public void onPrepareDialog(int id, Dialog dialog) { switch(id) {
case (QUAKE_DIALOG) :
SimpleDateFormat sdf;
sdf = new SimpleDateFormat(“dd/MM/yyyy HH:mm:ss”); String dateString = sdf.format(selectedQuake.getDate());
String quakeText = “Mangitude “ + selectedQuake.getMagnitude() + “\n” + selectedQuake.getDetails() + “\n” + selectedQuake.getLink();
AlertDialog quakeDialog = (AlertDialog)dialog; quakeDialog.setTitle(dateString); TextView tv =
(TextView)quakeDialog.findViewById(R.id.quakeDetailsTextView);
tv.setText(quakeText);
break;
}
}
11.4. The final step is to linkify the Dialog to make the link to the USGS a hyperlink. Adjust the Dialog box’s XML layout resource definition to include an autolink attribute.
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation=”vertical”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:padding=”10sp”>
<TextView
android:id=”@+id/quakeDetailsTextView”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:textSize=”14sp”
android:autoLink=”all”
/>
</LinearLayout>
Launch your activity again. When you click on a particular earthquake, a Dialog box will appear, par-tially obscuring the list, as shown in Figure 5-7.
Summar y
The focus of this chapter has been on binding your application components.
Intents provide a versatile messaging system that lets you pass intentions between your application and others, to perform actions and signal events. You learned how to use implicit and explicit Intents to start new Activities, and how to populate an Activity menu dynamically through runtime resolution of Activity Intent Filters.
You were introduced to Broadcast Intents and saw how they can be used to send messages throughout the device, particularly to support an event-driven model based on system- and application-specific events.
You learned how to use sub-Activities to pass data between Activities and how to use Dialogs to dis-play information and facilitate user input.
Adapters were introduced and used to bind underlying data to visual components. In particular, you saw how to use an Array Adapter and Simple Cursor Adapter to bind a List View to Array Lists and Cursors.
Finally, you learned the basics behind connecting to the Internet and using remote feeds as data sources for your native client applications.
You also learned:
❑To use Linkify to add implicit View Intents to TextViews at run time.
❑Which native Android actions are available for you to extend, replace, or embrace.
❑How to use Intent Filters to let your own Activities become handlers for completing action requests from your own or other applications.
❑ How to listen for Broadcast Intents using Broadcast Receivers. ❑ How to use an Activity as a Dialog box.
In the next chapter, you will learn how to persist information within your applications. Android pro-vides several mechanisms for saving application data, including files, simple preferences, and fully fea-tured relational databases (using the SQLite database library).