Android Application Development – Part 3


14 October 2011, by

previous article in the series

Recap

In Part 2 we retrieved GPS coordinates from the device and displayed them in the view.

In this part, we’ll be adding a little more functionality to the application, making sure that we can monitor the state of the GPS, requesting our own update frequency, and passing these coordinates into a web service to perform a ‘reverse geo-lookup': transforming coordinates into location names.

HINT: Using the emulator

A little time saver: The Android 3.0 emulator skin doesn’t add any helpful buttons by default (as it cannot assume that your device supports them). This can create a few issues getting around. Here are a few of the most useful Android buttons that are mapped to your keyboard:

  • HOME = the home button.
  • ESC = the back button.
  • Ctrl+F12 = move to next orientation (ie. from portrait to landscape).

Adding an Exit button

It’s useful to be able to exit the application (rather than just let Android push it to the background). To do that, we’ll add a button to the main layout, and provide some code in the main class to run the finish method when it’s clicked.

First, open up the main.xml layout using the Android Layout Editor (from the right-click menu on main.xml).

Drag a button from the Form Widgets in the Palette into the view. Now edit a few properties about it so that it’s called CloseButton and shows the text: close.

The XML view of main.xml should show something along these lines:

<button>
</button>

Now we need to hook up some code for this button. Open up GpsMonitoringActivity.java and add these lines to the onCreate method:

Button b = (Button) this.findViewById(R.id.CloseButton);

b.setOnClickListener(new OnClickListener() {
    public void onClick(View arg0) {
      close();
    }
  });

Elsewhere in the class, add a new method to handle closing the application:

/** Closes the application */
protected void close() {
  finish();
}

NB. This method could probably be in-lined, but for now it serves to help illustrate what we are doing and provides a little forward-compatibility: we could clean up other resources here too if necessary.

LocationListener and LocationManager

We’re going to refer to the LocationListener and LocationManager more than once, so we ought to keep them in private member variables. Add these entries to the top of the class:

private LocationListener _locationListener;
private LocationManager _locationManager;

Checking the GPS status

We’d like to be sure that the user has GPS enabled. So we’re going to make some fairly big changes to the way we do things in onCreate.

First, lets adjust onCreate to use our private LocationManager, and use it to perform a check for GPS:

// Acquire a reference to the system Location Manager
_locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

// ensure that GPS is switched on and available
if (!_locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
  showAlertMessageNoGps();
} else {
  registerForUpdates();
}

Elsewhere in the class, we’ll need to implement the showAlertMessageNoGps method:

/** Builds an alert message to allow the user the option of enabling GPS */
private void showAlertMessageNoGps() {
  final AlertDialog.Builder builder = new AlertDialog.Builder(this);
  builder
      .setMessage("Your GPS seems to be disabled, do you want to enable it?")
      .setCancelable(false)
      .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        public void onClick(final DialogInterface dialog, final int id) {
          launchGPSOptions();
        }
      }).setNegativeButton("No", new DialogInterface.OnClickListener() {
        public void onClick(final DialogInterface dialog, final int id) {
          dialog.cancel();
          close();
        }
      });
  final AlertDialog alert = builder.create();
  alert.show();
}

This method constructs a Yes/No dialogue, and asks the user if they’d like to visit their GPS settings. We allow them to do that by transmitting an Intent to open the system GPS settings. Add the launchGPSOptions method below to implement this:

  /** Launches the SecuritySettings activity */
  private void launchGPSOptions() {
    startActivityForResult(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0);
  }

Registering and deregistering for GPS updates

How frequently would we like our device to check for updates? Add this constant to the class:

private static final int GPS_FREQUENCY_MILLIS = 15000;

As it’s measured in milliseconds, it indicates that we’d like a GPS update approximately every 15 seconds. When used with the LocationManager‘s requestLocationUpdates method, it provides a hint to Android about how frequently it should collect updates.

We’ve not yet implemented the registerForUpdates method which will make use of the frequency indicator.
It comes as a pair with a deregisterForUpdates method too. Add these to the class:

/** Register the listener with the Location Manager to receive location updates */
private void registerForUpdates() {
  _locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, GPS_FREQUENCY_MILLIS, 0, _locationListener);
}

/** Register the listener with the Location Manager to receive location updates */
private void deregisterForUpdates() {
  _locationManager.removeUpdates(_locationListener);
}

NB. The android emulator will only provide Location updates to the application after you have provided fresh locations to the emulator (ie. through the DDMS interface or telnet – as seen in Part 2). The 15-second indicator above will manifest itself as a 15-second delay between your entering the second of two updates in quick succession, and that update becoming available to the application.

To return to the previous behaviour of the application being updated whenever you provide the emulator with a location, set the value to 0.

When to register and deregister

There are a few things left to do.

We ought to update the code that creates the LocationListener in onCreate as this class also ought to register and deregister for GPS properly. Alter the original code to use our private LocationListener as here:

// Define a listener that responds to location updates
_locationListener = new LocationListener() {
  public void onLocationChanged(Location location) {
    // Called when a new location is found by the network location provider.
    makeUseOfNewLocation(location);
  }

  public void onStatusChanged(String provider, int status, Bundle extras) {}

  public void onProviderEnabled(String provider) {
    registerForUpdates();
  }
  public void onProviderDisabled(String provider) {
    deregisterForUpdates();
  }
};

And while we’re doing this, lets consider the other time we ought to register and deregister: when pausing and resuming the application. Elsewhere in the class, override the onPause and onResume methods:

@Override
protected void onResume() {
  super.onResume();
  registerForUpdates();
}

@Override
protected void onPause() {
  super.onPause();
  deregisterForUpdates();
}

Reverse-Geocoding

Great – we’ve got a reliable stream of locations coming in. Let’s do something with them. Specifically, lets reverse-geocode them into an intersection of streets.

To do this, we’re going to use the Geonames library from geonames.org. Google also provides a reverse-geolocation API, but it’s not as convenient as this library, which neatly wraps all calls to the webservice.

There are 2 versions of this library available. I’ve used geonames-1.0.4-java1.5.jar. It needs a copy of jdom-1.0.jar too.

Right-click your project, and choose ‘New folder’ – create a folder called 3rdParty. Copy those jars into there, and now let’s add them to the build path:

Right-click the project again, and choose Properties. Open the Build Path section. On the Libraries tab, choose ‘Add Jars…’, open the 3rdParty folder, and add both geonames-1.0.4-java1.5.jar and jdom-1.0.jar.

Now, we need to adjust the permissions to allow our application to connect to the Geonames web service over the internet. Inside onCreate add:

// permit things like network access on the main thread
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
StrictMode.setThreadPolicy(policy);

This will remove the strict permissions that Android Honeycomb (and later editions) places on doing network access on your main thread.

Now we’ll need to add the INTERNET permission to AndroidManifest.xml. Add this line:


Now we can create a helper class to wrap up the geocoding activity. Create a new class called GeonamesReverseGeocoder and add this method:

/** Safely retrieves an intersection for the lat and long specified - or null */
public static Intersection doSearch(double latitude, double longitude) {
  try {
    return WebService.findNearestIntersection(latitude, longitude);

  } catch (Exception e) {
    return null;
  }
}

The most important thing this method does is to protect us from stray exceptions: the WebService is a 3rd party library.

Displaying the result

makeUseOfNewLocation needs a few edits to call through to the web service, and update the view:

/** Updates the location field with coordinates */
private void makeUseOfNewLocation(Location loc) {

  double lat = loc.getLatitude();
  double lng = loc.getLongitude();

  // prepare some update text with the new latitude and longitude
  String text = "lat.: " + lat + ", long.: " + lng;

  // now attempt to retrieve the intersection data for this location
  try {
    Intersection intersection = GeonamesReverseGeocoder.doSearch(lat, lng);

    if (intersection != null) {
      text = text + "n" + intersection.getStreet1() + ", " + intersection.getStreet2();

    } else {
      throw new IllegalStateException("The nearest intersection could not be found.");
    }

  } catch (Exception e) {
    // notify the user of any problems
    text = text + "n" + e.getMessage();
  }

  // display the update text
  Toast.makeText(getApplicationContext(),
      text,
      Toast.LENGTH_SHORT).show();
}

As you can see, getting the nearest Intersection is as simple as calling through to the GeonamesReverseGeocoder.doSearch static method – and then the code simply assembles a string describing what it has found.

Note that in the last few lines we’re using Android’s Toast class to display all the new information we’ve got. Toast is for creating and showing pop-up information of various durations and types. The makeText and show methods should be fairly self-explanatory!

Give it a try now! Run the application, and push in a few coordinates. A good test is a known location near where two streets intersect, such as these – in New York:

  • longitude: -73.9938
  • latitude: 40.7360
  • should be near intersection: W 14 St, 5th Ave

Application showing new location information in a 'toast'

Wrapping up!

So that’s the end of this tutorial. I hope you’ve found it useful – I’ve certainly found it  helpful myself to get down some of the things I’ve been learning as I go along. I’ve attached the source code below.

Happy developing!

Source code

Tags: , , , , , , ,

Categories: Mobile, Technical

«
»

2 Responses to “Android Application Development – Part 3”

  1. Mike says:

    Thanks for the code, but newer versions of Eclipse require the 3rd party .jar files to go in the lib/ folder of the project, or it will not work properly.

  2. Chris Harris says:

    Thanks for the tip Mike!


Leave a Reply

* Mandatory fields


7 × four =

Submit Comment