You are on page 1of 21

Android Location Based Services Application GPS location

With the incorporation of GPS devices in smartphones, Location Based Services(LBS) have become
pretty hot the past few years. The iPhone was the first to give a huge boost to this kind of
applications and now Android continues on the same path. In this tutorial, I will show you how to
build your first LBS application for Android. The first step is retrieving the users current location and
then using his coordinates to provide data for that location.

In general, the users pinpointing on the map is feasible in one of the following ways:

1. Using the GPS device that comes with the mobile


2. Using the ID of the Cell that the user is currently served by

The first one is much easier and more accurate (since the second provides only an approximation).
Since these days a big number of phones do have GPS devices onboard, we will use the first way.
The Android SDKs emulator can emulate changes in the users location and provide dummy data for
his coordinates.

Lets get started by creating a new Android Project in Eclipse. I gave it the fancy name
AndroidLbsGeocodingProject and used the properties as shown in the following image:

Note that I used the Android 1.5 platform version and 3 as the SDKs min version. The application
is not going to use any of the new super-duper APIs, so I decided to go with one of the first versions
for backwards compatibility. It is generally a good idea to support as many versions as possible. You
can find information regarding the platform versions and their corresponding market share here.

To start with, we will only add a button which will trigger the retrieval of the current locations
coordinates from a location provider. Thus, the main.xml file for the applications interface will be as
simple as this:
01 <?xml version="1.0" encoding="utf-8"?>
02 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03 android:orientation="vertical"
04 android:layout_width="fill_parent"
05 android:layout_height="fill_parent"
06 >

07 <Button
08 android:id="@+id/retrieve_location_button"
09 android:text="Retrieve Location"
10 android:layout_width="wrap_content"
11 android:layout_height="wrap_content"
12 />
13 </LinearLayout>

In order to get started with the location capabilities of the Android API, the first step is to take
reference of the LocationManager class, which provides access to the system location services. This
is done via the getSystemService of our activity (actually it inherits it from theContext parent class).
Then, we request updates of the devices location using the method requestLocationUpdates. In that
method, we provide the name of the preferred location provider (in our case GPS), the minimum
time interval for notifications (in milliseconds), the minimum distance interval for notifications (in
meters) and finally a class implementing the LocationListener interface. That interface declares
methods for handling changes in the users location as well as changes in the location providers status.
All the above can be translated into code as below:

01 package com.javacodegeeks.android.lbs;
02

03 import android.app.Activity;
04 import android.content.Context;
05 import android.location.Location;
06 import android.location.LocationListener;
07 import android.location.LocationManager;
08 import android.os.Bundle;

09 import android.view.View;
10 import android.view.View.OnClickListener;
11 import android.widget.Button;
12 import android.widget.Toast;

13
14 public class LbsGeocodingActivity extends Activity {
15

16 private static final long MINIMUM_DISTANCE_CHANGE_FOR_UPDATES = 1; // in


Meters
17 private static final long MINIMUM_TIME_BETWEEN_UPDATES = 1000; // in
Milliseconds
18
19 protected LocationManager locationManager;
20
21 protected Button retrieveLocationButton;
22

23 @Override
24 public void onCreate(Bundle savedInstanceState) {
25
26 super.onCreate(savedInstanceState);
27 setContentView(R.layout.main);
28
retrieveLocationButton = (Button)
29
findViewById(R.id.retrieve_location_button);
30
locationManager = (LocationManager)
31
getSystemService(Context.LOCATION_SERVICE);
32
33 locationManager.requestLocationUpdates(
34 LocationManager.GPS_PROVIDER,

35 MINIMUM_TIME_BETWEEN_UPDATES,
36 MINIMUM_DISTANCE_CHANGE_FOR_UPDATES,
37 new MyLocationListener()
38 );

39
40 retrieveLocationButton.setOnClickListener(new OnClickListener() {
41 @Override
42 public void onClick(View v) {
43 showCurrentLocation();
44 }
45 });
46
47 }
48
49 protected void showCurrentLocation() {
50
Location location =
51
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
52
53 if (location != null) {
54 String message = String.format(
"Current Location \n Longitude: %1$s \n Latitude:
55
%2$s",
56 location.getLongitude(), location.getLatitude()

57 );
58 Toast.makeText(LbsGeocodingActivity.this, message,
59 Toast.LENGTH_LONG).show();
60 }

61
62 }
63
64 private class MyLocationListener implements LocationListener {
65
66 public void onLocationChanged(Location location) {
67 String message = String.format(
68 "New Location \n Longitude: %1$s \n Latitude: %2$s",
69 location.getLongitude(), location.getLatitude()
70 );
Toast.makeText(LbsGeocodingActivity.this, message,
71
Toast.LENGTH_LONG).show();
72 }

73
74 public void onStatusChanged(String s, int i, Bundle b) {
Toast.makeText(LbsGeocodingActivity.this, "Provider status
75
changed",
76 Toast.LENGTH_LONG).show();
77 }
78

79 public void onProviderDisabled(String s) {


80 Toast.makeText(LbsGeocodingActivity.this,
81 "Provider disabled by the user. GPS turned off",
82 Toast.LENGTH_LONG).show();
83 }
84

85 public void onProviderEnabled(String s) {


86 Toast.makeText(LbsGeocodingActivity.this,
87 "Provider enabled by the user. GPS turned on",
88 Toast.LENGTH_LONG).show();
89 }
90
91 }
92
93 }

For the LocationListener interface, we implemented the MyLocationListener inner class. The
methods in that class just use Toasts to provide info about the GPS status or any location changes.
The only interface element is a Button which gets hooked up with aOnClickListener and when it is
clicked, the showCurrentLocation method is invoked. Then, the getLastKnownLocation of the
LocationManager instance is executed returning the last known Location. From a Location object we
can get information regarding the users altitude, latitude, longitude, speed etc. In order to be able to
run the above code, the necessary permissions have to be granted. These are:

ACCESS_FINE_LOCATION
ACCESS_MOCK_LOCATION
ACCESS_COARSE_LOCATION

Include those in the AndroidManifest.xml file, which will be as follows:

01 <?xml version="1.0" encoding="utf-8"?>


02
03 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
04 package="com.javacodegeeks.android.lbs"

05 android:versionCode="1"
06 android:versionName="1.0">
07

08 <application android:icon="@drawable/icon" android:label="@string/app_na


me">
09 <activity android:name=".LbsGeocodingActivity"
10 android:label="@string/app_name">

11 <intent-filter>
12 <action android:name="android.intent.action.MAIN" />
13 <category android:name="android.intent.category.LAUNCHER" />
14 </intent-filter>
15 </activity>
16
17 </application>
18

19 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
/>
20 <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />

21 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /
>
22
23 <uses-sdk android:minSdkVersion="3" />
24
25 </manifest>

Next, use the AVD Manager in order to create a new Android device and make sure that GPS
support is included in the features:

Next, use Run> Run Configurations to create a new configuration for the project:

If you hit Run, the emulator is started, but nothing will really happen when the button is clicked. That
is because when the emulator starts, there is no last known location to be retrieved
(the Location instance that is returned is null).

We have to feed the emulator with some dummy data. Go to the DDMS view of Eclipse and look for
the Emulation Control tab. There, among other things, you will find the Location Controls section,
which can send mock location data to the emulator. In the Manual tab, just hit the Send button,
there are already some coordinates set up.
When the data are sent to the emulators GPS device, our listener will be triggered and the current
location will be printed in the screen in the form of a Toast notification.

Now, a last known location exists, so if you hit the Retrieve Location button, a not null location will
be fetched and the coordinates will again be printed in the screen:
Thats it. Now you are able to emulate changes in the users location and retrieve updates about
them. In the next tutorial, I will show you how to leverage those coordinates in order to provide useful
data to the user.

Happy coding!!!
Android Reverse Geocoding with Yahoo API PlaceFinder

In my previous tutorial (Android Location Based Services Application GPS location) I showed you
how to retrieve the users current location in the form of latitude and longitude coordinates. Using
those coordinates we are going to provide information regarding the users position. For example,
we are going to find the street address nearest to a specific point. That process is called reverse
Geocoding and it is the reverse process of Geocoding, which involves finding associated geographic
coordinates (often expressed as latitude and longitude) from other geographic data, such as street
addresses, or zip codes (postal codes).

For that purpose, we are going to use Yahoo! PlaceFinder, a super cool API for geocoding and
reverse geocoding. From the official site:

Yahoo! PlaceFinder is a geocoding Web service that helps developers make their applications
location-aware by converting street addresses or place names into geographic coordinates (and vice
versa).

In order to use the API, you are going to need a key (totally free). However, for the purposes of this
tutorial, a key is not required. We are going to use the sample request which can be reached here.
The response is in XML format so an XML parser will be needed. Dont worry though, since Android
has included XML capabilities in its core API.

Another thing to note is that PlaceFinder is a web API, so in order to be used by an Android
application, the device has to be connected to the well web. Moreover, an HTTP client will have
to be used by our application. Again, dont worry because the Android API includes Apaches HTTP
Client for that purpose.

So, lets get started. We are going to continue from where we left the previous tutorial. Please check
it out if you havent already read it. You can download the Eclipse project from here.

First, we add a new Button which will trigger the reverse geocoding procedure. Thus, the main.xml
file for the user interface becomes as follows:

01 <?xml version="1.0" encoding="utf-8"?>


02 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03 android:orientation="vertical"
04 android:layout_width="fill_parent"
05 android:layout_height="fill_parent"
06 >

07 <Button
08 android:id="@+id/retrieve_location_button"
09 android:text="Retrieve Location"
10 android:layout_width="wrap_content"
11 android:layout_height="wrap_content"
12 />

13 <Button
14 android:id="@+id/reverse_geocoding_button"
15 android:text="Reverse Geocoding"
16 android:layout_width="wrap_content"
17 android:layout_height="wrap_content"
18 />
19 </LinearLayout>

The new button gets hooked up with a OnClickListener and when it is clicked, the
performReverseGeocodingInBackground method is invoked. In that method, the current location is
retrieved (as shown in the previous tutorial). A Location instance object, named currentLocation is
used in order to store the users latest known location. Then, the reverse geocoding procedure gets
started.

Note that the operation will take place in the background, meaning it will execute in a thread other
than the main UI thread. This is because Android is very sensitive in responsiveness issues.
Fetching data from the internet can be a time consuming operation and we dont want to pause the
main thread while the HTTP client waits for the data to be downloaded. Also note that stalling the
main thread UI for more than five seconds will trigger an Application Not Responding (ANR) dialog,
where the user is given the opportunity to kill your application. Not very good.

For that reason, we will execute the data retrieval operation in a new thread, leveraging the Android
API. The class that we will use extends the AsyncTask and is named
ReverseGeocodeLookupTask. From the official documentation page, AsyncTask enables proper
and easy use of the UI thread. This class allows to perform background operations and publish
results on the UI thread without having to manipulate threads and/or handlers.

There are three methods that we will override. First, onPreExecute, which occurs in the main thread
and is generally used for initialization purposes. In our implementation, we use
a ProgressDialog widget to let the user know that some operation is taking place. Second,
thedoInBackground method, in which a computation is performed on a background thread and a
result object (defined by the subclass of this task) is returned. In our implementation, the XML data
are retrieved and parsed (as I will show you in a minute) and we pass back a domain model class
named GeoCodeResult which is a placeholder for the location data. Third, the onPostExecute,
which also runs on the UI thread and is used for cleanup purposes. In our case,
the ProgressDialog is cancelled and a Toast notification presents the retrieved data to the user.
Incorporating all the above, the code for our main Activity is now the following:

001 package com.javacodegeeks.android.lbs;


002

003 import android.app.Activity;


004 import android.app.ProgressDialog;
005 import android.content.Context;
006 import android.location.Location;
007 import android.location.LocationListener;
008 import android.location.LocationManager;
009 import android.os.AsyncTask;
010 import android.os.Bundle;

011 import android.view.View;


012 import android.view.View.OnClickListener;
013 import android.widget.Button;
014 import android.widget.Toast;

015
016 import com.javacodegeeks.android.lbs.model.GeoCodeResult;
017 import com.javacodegeeks.android.lbs.services.GeoCoder;
018
019 public class LbsGeocodingActivity extends Activity {
020

021 private static final long MINIMUM_DISTANCE_CHANGE_FOR_UPDATES = 1; // in


Meters

022 private static final long MINIMUM_TIME_BETWEEN_UPDATES = 1000; // in


Milliseconds
023
024 private GeoCoder geoCoder = new GeoCoder();
025
026 protected LocationManager locationManager;
027 protected Location currentLocation;
028
029 protected Button retrieveLocationButton;
030 protected Button reverseGeocodingButton;
031
032 @Override
033 public void onCreate(Bundle savedInstanceState) {
034
035 super.onCreate(savedInstanceState);
036 setContentView(R.layout.main);

037
retrieveLocationButton = (Button)
038
findViewById(R.id.retrieve_location_button);
reverseGeocodingButton = (Button)
039
findViewById(R.id.reverse_geocoding_button);
040
locationManager = (LocationManager)
041
getSystemService(Context.LOCATION_SERVICE);
042
043 locationManager.requestLocationUpdates(
044 LocationManager.GPS_PROVIDER,

045 MINIMUM_TIME_BETWEEN_UPDATES,
046 MINIMUM_DISTANCE_CHANGE_FOR_UPDATES,
047 new MyLocationListener()
048 );

049
050 retrieveLocationButton.setOnClickListener(new OnClickListener() {
051 @Override
052 public void onClick(View v) {
053 showCurrentLocation();
054 }
055 });
056

057 reverseGeocodingButton.setOnClickListener(new OnClickListener()


{
058 @Override
059 public void onClick(View v) {
060 performReverseGeocodingInBackground();
061 }
062 });
063
064 }

065
066 protected void performReverseGeocodingInBackground() {
067 showCurrentLocation();
068 new ReverseGeocodeLookupTask().execute((Void[])null);
069 }
070
071 protected void showCurrentLocation() {
072
currentLocation =
073
locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
074

075 if (currentLocation != null) {


076 String message = String.format(
"Current Location \n Longitude: %1$s \n Latitude:
077
%2$s",
currentLocation.getLongitude(),
078
currentLocation.getLatitude()
079 );
080 Toast.makeText(LbsGeocodingActivity.this, message,
081 Toast.LENGTH_LONG).show();
082 }

083
084 }
085
086 private class MyLocationListener implements LocationListener {
087
088 public void onLocationChanged(Location location) {
089 String message = String.format(
090 "New Location \n Longitude: %1$s \n Latitude: %2$s",
091 location.getLongitude(), location.getLatitude()
092 );
Toast.makeText(LbsGeocodingActivity.this, message,
093
Toast.LENGTH_LONG).show();
094 }

095
096 public void onStatusChanged(String s, int i, Bundle b) {
Toast.makeText(LbsGeocodingActivity.this, "Provider status
097
changed",
098 Toast.LENGTH_LONG).show();
099 }
100

101 public void onProviderDisabled(String s) {


102 Toast.makeText(LbsGeocodingActivity.this,
103 "Provider disabled by the user. GPS turned off",
104 Toast.LENGTH_LONG).show();
105 }
106

107 public void onProviderEnabled(String s) {


108 Toast.makeText(LbsGeocodingActivity.this,
109 "Provider enabled by the user. GPS turned on",
110 Toast.LENGTH_LONG).show();
111 }
112
113 }
114

115 public class ReverseGeocodeLookupTask extends AsyncTask <Void, Void,


GeoCodeResult> {
116
117 private ProgressDialog progressDialog;
118

119 @Override
120 protected void onPreExecute() {
121 this.progressDialog = ProgressDialog.show(
122 LbsGeocodingActivity.this,

123 "Please wait...contacting Yahoo!", // title


124 "Requesting reverse geocode lookup", // message
125 true // indeterminate
126 );
127 }
128

129 @Override
130 protected GeoCodeResult doInBackground(Void... params) {
131 return geoCoder.reverseGeoCode(currentLocation.getLatitude(),
currentLocation.getLongitude());
132 }

133
134 @Override
135 protected void onPostExecute(GeoCodeResult result) {
136 this.progressDialog.cancel();
Toast.makeText(LbsGeocodingActivity.this, result.toString(),
137
Toast.LENGTH_LONG).show();
138 }
139
140 }
141
142 }

In order to keep things simple and separate the applications concerns, we are going to use multiple
small classes. The main activity uses a GeoCoder service, our custom implementation which uses the
Yahoo API. The code for that class is the following:
01 package com.javacodegeeks.android.lbs.services;
02
03 import com.javacodegeeks.android.lbs.model.GeoCodeResult;
04
05 public class GeoCoder {
06

private static final String YAHOO_API_BASE_URL


07 = "http://where.yahooapis.com/geocode?q=%1$s,+
%2$s&gflags=R&appid=[yourappidhere]";
08
09 private HttpRetriever httpRetriever = new HttpRetriever();
10 private XmlParser xmlParser = new XmlParser();

11
12 public GeoCodeResult reverseGeoCode(double latitude, double longitude) {
13
String url = String.format(YAHOO_API_BASE_URL,
14
String.valueOf(latitude), String.valueOf(longitude));
15 String response = httpRetriever.retrieve(url);
16 return xmlParser.parseXmlResponse(response);
17
18 }

19
20 }

The model object class named GeoCodeResult will be described later. The GeoCoder service uses
two others services.

First we have the HttpRetriever class which uses the Apache HTTP client API to perform a GET request
and retrieve the data from the Yahoo API URL. The code is the following:

01 package com.javacodegeeks.android.lbs.services;
02
03 import java.io.IOException;
04

05 import org.apache.http.HttpEntity;
06 import org.apache.http.HttpResponse;
07 import org.apache.http.client.methods.HttpGet;
08 import org.apache.http.impl.client.DefaultHttpClient;
09 import org.apache.http.util.EntityUtils;
10
11 import android.util.Log;
12
13 public class HttpRetriever {
14
15 private final String TAG = getClass().getSimpleName();
16
17 private DefaultHttpClient client = new DefaultHttpClient();
18
19 public String retrieve(String url) {
20
21 HttpGet get = new HttpGet(url);
22
23 try {
24

25 HttpResponse getResponse = client.execute(get);


26 HttpEntity getResponseEntity = getResponse.getEntity();
27
28 if (getResponseEntity != null) {
29 String response = EntityUtils.toString(getResponseEntity);
30 Log.d(TAG, response);
31 return response;
32 }

33
34 } catch (IOException e) {
35 e.printStackTrace();
36 }

37
38 return null;
39
40 }
41
42 }

Easy stuff here. A DefaultHttpClient instance is used for the HTTP requests. A HttpGet is used to
represent a GET request and is passed as an argument to the execute method of the client.
A HttpResponse is retrieved as a result (in successful operations) and from that we get
an HttpEntity object. Finally, the EntityUtils helper class is used to convert the response to a String
and that response is returned to the caller. Note that no control of unsuccessful operations has been
implemented, please take care of that if you want something more robust.
Next service to exam is the XmlParser class. This uses the existing DOM manipulation classes to
perform parsing of the XML response. From the APIs response (example here), we are going to use
only the line1 line4 nodes of the ResultSet/Result element. The implementation is a typical DOM
one, so I will not get into many details. Here is the code:

01 package com.javacodegeeks.android.lbs.services;
02
03 import java.io.StringReader;
04

05 import javax.xml.parsers.DocumentBuilder;
06 import javax.xml.parsers.DocumentBuilderFactory;
07
08 import org.w3c.dom.Document;
09 import org.w3c.dom.Node;
10 import org.w3c.dom.NodeList;
11 import org.xml.sax.InputSource;
12
13 import android.util.Log;
14
15 import com.javacodegeeks.android.lbs.model.GeoCodeResult;
16
17 public class XmlParser {
18
19 private final String TAG = getClass().getSimpleName();
20
21 public GeoCodeResult parseXmlResponse(String response) {
22
23 try {
24
DocumentBuilderFactory dbf =
25
DocumentBuilderFactory.newInstance();
26 DocumentBuilder db = dbf.newDocumentBuilder();
Document doc =
27
db.parse(new InputSource(new StringReader(response)));
28
NodeList resultNodes =
29
doc.getElementsByTagName("Result");
30 Node resultNode = resultNodes.item(0);
31 NodeList attrsList = resultNode.getChildNodes();
32
33 GeoCodeResult result = new GeoCodeResult();
34
35 for (int i=0; i < attrsList.getLength(); i++) {
36
37 Node node = attrsList.item(i);
38 Node firstChild = node.getFirstChild();

39

40 if ("line1".equalsIgnoreCase(node.getNodeName()) &&
firstChild!=null) {
41 result.line1 = firstChild.getNodeValue();
42 }

43 if ("line2".equalsIgnoreCase(node.getNodeName()) &&
firstChild!=null) {
44 result.line2 = firstChild.getNodeValue();

45 }

46 if ("line3".equalsIgnoreCase(node.getNodeName()) &&
firstChild!=null) {
47 result.line3 = firstChild.getNodeValue();
48 }

49 if ("line4".equalsIgnoreCase(node.getNodeName()) &&
firstChild!=null) {
50 result.line4 = firstChild.getNodeValue();
51 }
52 }

53
54 Log.d(TAG, result.toString());
55
56 return result;
57
58 }

59 catch (Exception e) {
60 e.printStackTrace();
61 }
62
63 return null;
64
65 }
66
67 }
In short, we begin from the root element, we travel down to the Result node and then we get all this
nodes children. From those, we sort out the elements we care about using
the getNodeName method and finally we retrieve the node value using the getNodeValuemethod
executed against the first child of the node. The model class is populated accordingly and then returned
back. The code for that class is:

01 package com.javacodegeeks.android.lbs.model;
02
03 public class GeoCodeResult {
04
05 public String line1;
06 public String line2;
07 public String line3;
08 public String line4;
09
10 @Override
11 public String toString() {
12
13 StringBuilder builder = new StringBuilder();
14 builder.append("Location:");

15
16 if (line1!=null)
17 builder.append("-"+line1);
18 if (line2!=null)
19 builder.append("-"+line2);
20 if (line3!=null)
21 builder.append("-"+line3);
22 if (line4!=null)
23 builder.append("-"+line4);
24
25 return builder.toString();
26
27 }
28
29 }

The final step is to add the permission required to access the internet, i.e.
android.permission.INTERNET. Launch the emulator using the already created Run
Configuration. Go to the DDMS view and set the coordinates to a known location. I used the ones
that the PlaceFinder sample request uses in order to make sure that a valid response will be
generated. Hit Send in order to update the users last known
location and then click the Reverse Geocoding button of our application. First, the progress dialog
appears, informing us about the background operation that takes

place. After the data are retrieved and extracted from the API
response, a Toast appears and let us know what the current address is, as shown

below: Thats it! You can now build your location-aware


applications and test them using the Android emulator. Happy mobile coding!!!