[align=center]See also: >> Part II / II <<[/align]
[align=center]View: >> Full Source for this Tutorial <<[/align]
What is this: This tutorial shows how to create a relatively complex application, that takes advantage of LocationManager (GPS-Access), to show your contacts on a Map.
What you will learn:
- You will learn how to extend the MapActivity to create a Map-Application.
- Use the LocationManager to track your GPS-Position.
- Work with Cursors to grab data from the contents.
- Override the onFreeze(), onResume(), onXYZ()-methods which are i.e. called, when another activity gets on top of ours.
- Draw things on the MapView using an OverlayController.
- How to create menus.
- Many more.....

Difficulty: 3.5 of 5

What it will look like:
1. Always updating Screen
2. FriendFinder-Map toggled to SatelliteView
3. FriendFinder-Map toggled to Streets-View (also updating ^^ own position got outside of the map)
[align=center]



Description:
So the first thing as always is, to think about what we want to do and what we need for that...
- We want a ListView of our Contacts --> We need a ListView (awesome conclusion...
)
- We want to do sth. with geo-Location(GPS) --> We need a LocationManager and his friends
- We want to get the location of our friends --> We need a place to define them!
- We want to display a Map --> We need a second (Map)Activity
a. We need to register that second Activity - We want to draw sth over the Map --> We need an OverlayController

[align=center] As a human you can see the format of the URI: "geo:[-]XXX.XXXXX,[-]XXX.XXXXXX#".
Simply setup two or three sample-contacts (with different GPS-Locations) just like in the image below.
Or take these:
geo:37.402346,-122.075014# (Google-Headquarters, SF, California)
geo:37.444608,-122.216034# (McDonalds,

geo:37.41622,-122.089919# (Starbucks, somewhere in SF, California)
or get your own on GoogleMaps (Hover the "Link to this Page"-Link)

[align=center]>> Part I / II <<[/align]
[align=center]GoTo: >> Part II / II <<[/align]
0. Create an new Android-Project (HowTo).


As we are planning to access the Contacts of our Android-Phone, we make that public in the AndroidManifest.xml . If we wouldn't do that, our Application would 'crash'(Exception followed by App-End) right when we would try to access the Contacts for the first time.
Using xml Syntax Highlighting
- ...
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.anddev.android.friendfinder">
- <uses-permission id="android.permission.READ_CONTACTS"/>
- <application android:icon="@drawable/icon">
- <activity class=".FriendFinder" android:label="@string/main_title">
- ...
Parsed in 0.001 seconds, using GeSHi 1.0.8.4
<?xml version="1.0" encoding="utf-8"?>
We need to define all the Strings we are going to use in our application in the 'res/values/strings.xml'.
(We could hardcode them too, but that would be very bad

Using xml Syntax Highlighting
- <resources>
- <string name="main_title">FriendFinder - anddev.org</string>
- <string name="main_menu_open_map">Open visual FriendTracker</string>
- <string name="main_list_format">$name ($distance km)</string>
- <string name="main_list_geo_not_set">not set</string>
- <string name="map_title">FriendFinder - anddev.org</string>
- <string name="map_menu_zoom_in">Zoom in (Key: I)</string>
- <string name="map_menu_zoom_out">Zoom out (Key: O)</string>
- <string name="map_menu_back_to_list">Back to list</string>
- <string name="map_menu_toggle_street_satellite">Toggle View: Street / Satellite (Key: T)</string>
- <string name="map_overlay_own_name">Me</string>
- </resources>
Parsed in 0.001 seconds, using GeSHi 1.0.8.4
We will also have a class: 'Friend' (in Friend.java) which combines a Name with a Location:
Using java Syntax Highlighting
- public class Friend{
- public Location itsLocation = null;
- public String itsName = null;
- public Friend(Location aLocation, String aName){
- this.itsLocation = aLocation;
- this.itsName = aName;
- }
- }
Parsed in 0.010 seconds, using GeSHi 1.0.8.4
1. So lets start where all Activities start:
Using java Syntax Highlighting
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- /* The first thing we need to do is to setup our own
- * locationManager, that will support us with our own gps data */
- this.myLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
- /* Update the list of our friends once on the start,
- * as they are not(yet) moving, no updates to them are necessary */
- this.refreshFriendsList();
- /* Initiate the update of the contactList
- * manually for the first time */
- this.updateList();
- /* Prepare the things, that will give
- * us the ability, to receive Information
- * about our GPS-Position. */
- this.setupForGPSAutoRefreshing();
- }
Parsed in 0.010 seconds, using GeSHi 1.0.8.4
2. Lets dig deeper into the refreshFriendsList()-Method, which will grab our Contacts/Friends out of Androids Database using a query/Cursor.
As it is a pretty big function doing some advanced logic, we will split into pieces here.
The first thing we will do is to grab a so called Cursor. I'll try to explain that referring to relational databases and their SQL (Structured Query Language).
A Cursor is kinda like the result of a SQL-Query. In this case we make a query on 'People.CONTENT_URI' what would referr to sth. like 'SELECT * FROM 'Contacts'.
The next 3 arguments remain 'null', we could have filled them stuff like the equivalent to 'WHERE Name='Peter''...
The last field contains the Sort-Order we want to receive. Here the similarity to SQL is pretty obvious. We chose sorting the results by People.NAME in an ASCending order.
Using java Syntax Highlighting
- /** List of friends in */
- protected ArrayList<Friend> allFriends = new ArrayList<Friend>();
- // ...
- private void refreshFriendsList(){
- Cursor c = getContentResolver().query(People.CONTENT_URI,
- null, null, null, People.NAME + " ASC");
- /* This method allows the activity to take
- * care of managing the given Cursor's lifecycle
- * for you based on the activity's lifecycle. */
- this.startManagingCursor(c);
- int notesColumn = c.getColumnIndex(People.NOTES);
- int nameColumn = c.getColumnIndex(People.NAME);
- /* This will take all our contacts transformed to Strings */
- ArrayList<String> listItems = new ArrayList<String>();
Parsed in 0.011 seconds, using GeSHi 1.0.8.4
We are now going to loop through every contact that the Cursor found/'is providing' and check whether the Notes-String of that Contact somwhere contains a 'geo:XX...XX#' within.
Using java Syntax Highlighting
- // Moves the cursor to the first row
- // and returns true if there is sth. to get
- if (c.first()) {
- do {
- String notesString = c.getString(notesColumn);
- Location friendLocation = null;
Parsed in 0.010 seconds, using GeSHi 1.0.8.4
If there was anything saved within the Notes of this contact, we now check whether there is a 'geo:XX...XX#' within the Notes-String. If we wound one, we extract the Latitude/Longitude and save it to 'friendLocation'.
[align=center]Learn about Regular Expressions in Java.[/align]
Using java Syntax Highlighting
- if (notesString != null) {
- // Pattern for extracting geo-ContentURIs from the notes.
- final String geoPattern = "(geo:[\\-]?[0-9]{1,3}\\.[0-9]{1,6}\\,[\\-]?[0-9]{1,3}\\.[0-9]{1,6}\\#)";
- // Compile and use regular expression
- Pattern pattern = Pattern.compile(geoPattern);
- CharSequence inputStr = notesString;
- Matcher matcher = pattern.matcher(inputStr);
- boolean matchFound = matcher.find();
- if (matchFound) {
- // We take the first match available
- String groupStr = matcher.group(0);
- // And parse the Lat/Long-GeoPos-Values from it
- friendLocation = new Location();
- String latid = groupStr.substring(groupStr.indexOf(":") + 1,
- groupStr.indexOf(","));
- String longit = groupStr.substring(groupStr.indexOf(",") + 1,
- groupStr.indexOf("#"));
- friendLocation.setLongitude(Float.parseFloat(longit));
- friendLocation.setLatitude(Float.parseFloat(latid));
- }
- }
Parsed in 0.012 seconds, using GeSHi 1.0.8.4
So, if the friendLocation is passed can be not null (if we found a 'geo...# within the notes), but can also be still as we found nothing. Now we close the loop to iterate over every Contact.
Using java Syntax Highlighting
- if(friendLocation != null){
- String friendName = c.getString(nameColumn);
- allFriends.add(new Friend(friendLocation, friendName));
- }
- } while (c.next());
Parsed in 0.010 seconds, using GeSHi 1.0.8.4

3. Now lets hop into the next function that had been called in onCreate(...):
Using java Syntax Highlighting
- /* Initiate the update of the contactList
- * manually for the first time */
- this.updateList();
Parsed in 0.010 seconds, using GeSHi 1.0.8.4
This method will display all contacts + their distance to ourself, as you've seen it in the animated GIF above.
The first thing we are doing with in is to get our own GPS-Location.




Using java Syntax Highlighting
- private void updateList() {
- // Refresh our location...
- this.myLocation = myLocationManager.getCurrentLocation("gps");
Parsed in 0.012 seconds, using GeSHi 1.0.8.4
So, 'this.myLocation' is of the type "Location" which now contains mainly a "Longitude and Latitude"-Value, what together is our exact location on planet earth.
Now we loop over 'this.allFriends' and determine our distance towards each and save it in a ArrayList<String>, IF they are not farer away than the static field: NEARFRIEND_MAX_DISTANCE.
Using java Syntax Highlighting
- private void updateList() {
- // Refresh our location...
- this.myLocation = myLocationManager.getCurrentLocation("gps");
- ArrayList<String> listItems = new ArrayList<String>();
- // For each Friend
- for(Friend aNearFriend : this.allFriends){
- /* Load the row-entry-format defined as a String
- * and replace $name with the contact's name we
- * get from the cursor */
- String curLine = new String(getString(R.string.main_list_format));
- curLine = curLine.replace("$name", aNearFriend.itsName);
- if(aNearFriend.itsLocation != null){
- if( this.myLocation.distanceTo(aNearFriend.itsLocation) <
- NEARFRIEND_MAX_DISTANCE){
- final DecimalFormat df = new DecimalFormat("####0.000");
- String formattedDistance =
- df.format(this.myLocation.distanceTo(
- aNearFriend.itsLocation) / 1000);
- curLine = curLine.replace("$distance", formattedDistance);
- }
- }else{
- curLine = curLine.replace("$distance",
- getString(R.string.main_list_geo_not_set));
- }
- listItems.add(curLine);
- }
- ArrayAdapter<String> notes = new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_1, listItems);
- this.setListAdapter(notes);
- }
Parsed in 0.013 seconds, using GeSHi 1.0.8.4
Now we only have to apply that ArrayList<String> to be put into our ListView (what we can see actually) with the following lines:
Using java Syntax Highlighting
- ArrayAdapter<String> notes = new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_1, listItems);
- /* Save the index of the selected item
- * (if there has been an ListAdapter set before <img src="http://www.anddev.org/images/smilies/icon_exclaim.gif" alt=":!:" title="Exclamation" />) */
- long beforeIndex = 0;
- if(this.getListAdapter() != null)
- beforeIndex = this.getSelectionRowID();
- this.setListAdapter(notes);
- // Try to apply the saved selection-index
- try{
- this.setSelection((int)beforeIndex);
- }catch (Exception e){}
- }
Parsed in 0.013 seconds, using GeSHi 1.0.8.4
We could start our Application by now but the values would not yet update. But as we want them to update like this:
[align=center]

We need to do some more "System"-Stuff. Don't panic, there will be ~3x comments than Lines of real code.
4. Remembering the onCreate(..)-method from the very beginning, there was another method left within:
Using java Syntax Highlighting
- /* Prepare the things, that will give
- * us the ability, to receive Information
- * about our GPS-Position. */
- this.setupForGPSAutoRefreshing();
Parsed in 0.012 seconds, using GeSHi 1.0.8.4
So lets get into that heavily commented function:
Using java Syntax Highlighting
- protected final long MINIMUM_DISTANCECHANGE_FOR_UPDATE = 25; // in Meters
- protected final long MINIMUM_TIME_BETWEEN_UPDATE = 2500; // in Milliseconds
- // ....
- /** Register with our LocationManager to send us
- * an intent (who's Action-String we defined above)
- * when an intent to the location manager,
- * that we want to get informed on changes to our own position.
- * This is one of the hottest features in Android.
- */
- private void setupForGPSAutoRefreshing() {
- // Get the first provider available
- List<LocationProvider> providers = this.myLocationManager.getProviders();
- LocationProvider provider = providers.get(0);
- this.myLocationManager.requestUpdates(provider, MINIMUM_TIME_BETWEEN_UPDATE,
- MINIMUM_DISTANCECHANGE_FOR_UPDATE,
- new Intent(MY_LOCATION_CHANGED_ACTION));
- /* Create an IntentReceiver, that will react on the
- * Intents we said to our LocationManager to send to us. */
- this.myIntentReceiver = new MyIntentReceiver();
- /*
- * In onResume() the following method will be called:
- * registerReceiver(this.myIntentReceiver, this.myIntentFilter);
- */
- }
Parsed in 0.014 seconds, using GeSHi 1.0.8.4
We used the following specialized IntentReceiver above, that will simply call our updateList()-method when it receives an Intent. (As we will apply our filter to it, it will only receive 'MY_LOCATION_CHANGED_ACTION'-Intents)
The following to Classes/Objects were used above and are working "as a team"

"( registerReceiver(this.myIntentReceiver, this.myIntentFilter); ")
Using java Syntax Highlighting
- protected final IntentFilter myIntentFilter = new IntentFilter(MY_LOCATION_CHANGED_ACTION);
- //....
- /**
- * This tiny IntentReceiver updates
- * our stuff as we receive the intents
- * (LOCATION_CHANGED_ACTION) we told the
- * myLocationManager to send to us.
- */
- class MyIntentReceiver extends IntentReceiver {
- @Override
- public void onReceiveIntent(Context context, Intent intent) {
- if(FriendFinder.this.doUpdates)
- // Will simply update our list, when receiving an intent
- FriendFinder.this.updateList();
- }
- }
Parsed in 0.014 seconds, using GeSHi 1.0.8.4
5. The last thing we would have to do is to overwrite the onResume() and onFreeze(...)-Functions, as we want to (un)register as the Application is the top-most or not. (Prevents unnecessary CPU-Usage and therefor Battery-Consumption).
[align=center]:idea: For further information about those onWhatEver()-Methods have a look here: Lifecycle of an Activity.[/align]
Using java Syntax Highlighting
- /**
- * Restart the receiving, when we are back on line.
- */
- @Override
- public void onResume() {
- super.onResume();
- this.doUpdates = true;
- /* As we only want to react on the LOCATION_CHANGED
- * intents we made the OS send out, we have to
- * register it along with a filter, that will only
- * "pass through" on LOCATION_CHANGED-Intents.
- */
- this.registerReceiver(this.myIntentReceiver, this.myIntentFilter);
- }
- /**
- * Make sure to stop the animation when we're no longer on screen,
- * failing to do so will cause a lot of unnecessary cpu-usage!
- */
- @Override
- public void onFreeze(Bundle icicle) {
- this.doUpdates = false;
- this.unregisterReceiver(this.myIntentReceiver);
- super.onFreeze(icicle);
- }
Parsed in 0.014 seconds, using GeSHi 1.0.8.4
[align=center]You find the full code at the very very very end of this post.
:!: Congratulations, you've just created a auto-refreshing, gps-using Killer-Application

But this was just the first part! We want to do some Map-Stuff

[align=center]GoTo: >> Part II / II <<[/align]
[align=center]View: >> Full Source for this Tutorial <<[/align]
Regards,
plusminus