andbook!.pdf - Learning Android Get an anddev.org - Android-Shirt Back to index
anddev.org Header Logo
FAQ Search Top rated articles Browse Feeds anddev.org - Authors Contact Details Register Log in

The Friend Finder - MapActivity using GPS - PART: I / II


 
       anddev.org - Android Development Community | Android Tutorials | Index -> Map Tutorials
Author Message
plusminus
Site Admin
Site Admin


Joined: 14 Nov 2007
Posts: 2439
Location: College Park, MD

PostPosted: Thu Nov 22, 2007 9:04 pm    Post subject: The Friend Finder - MapActivity using GPS - PART: I / II Reply with quote

The Friend Finder - MapActivity using GPS - Part: I / II




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.....


Question Problems/Questions: post right below...

Difficulty: 3.5 of 5 Smile

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)



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... Rolling Eyes )
  • We want to do sth. with geo-Location(GPS) --> We need a LocationManager and his friends Wink
  • 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


Idea As we need to store the GPS-Location of our contacts somewhere, I chose the Notes-Section in each Contacts-Profile. Saving a GPS-location is pretty simple, as we choose a well-structured style of data: a URI-String, which can easily found and extracted from the Notes-String using Regular Expressions. (all that is explained in details below)

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, Rolling Eyes near MountainView, SF, California)
geo:37.41622,-122.089919# (Starbucks, somewhere in SF, California)
or get your own on GoogleMaps (Hover the "Link to this Page"-Link)



>> Part I / II <<



0. Create an new Android-Project (HowTo).
Idea I called it "FriendFinder" as that is an extremely perfect name Rolling Eyes

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.
XML:
  ...
<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">
   ...

<?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 Crying or Very sad )
XML:
<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>


We will also have a class: 'Friend' (in Friend.java) which combines a Name with a Location:
Java:
public class Friend{
     public Location itsLocation = null;
     public String itsName = null;
     public Friend(Location aLocation, String aName){
          this.itsLocation = aLocation;
          this.itsName = aName;
     }
}


1. So lets start where all Activities start:
Java:
     /** 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();
     }


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.
Java:
     /** 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>();

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.
Java:
          // 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;

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'.
Learn about Regular Expressions in Java.

Java:
                    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));
                         }
                    }

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.
Java:
                    if(friendLocation != null){
                         String friendName = c.getString(nameColumn);
                         allFriends.add(new Friend(friendLocation, friendName));
                    }
               } while (c.next());

Idea All of our friends are now stored to the field 'this.allFriends'.

3. Now lets hop into the next function that had been called in onCreate(...):
Java:
          /* Initiate the update of the contactList
           * manually for the first time */

          this.updateList();

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.
Question Question: How will that work, we are in an emulator, with no gps-receiver connected Question
Idea Answer: Within the emulator there is running a pseudo-gps-receiver, that simulates a person driving/walking around in San Francisco, close to the Google-Headquarters! Rolling Eyes
Java:
     private void updateList() {
          // Refresh our location...
          this.myLocation = myLocationManager.getCurrentLocation("gps");

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.
Java:
     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);
     }

Now we only have to apply that ArrayList<String> to be put into our ListView (what we can see actually) with the following lines:
Java:
          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 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){}
     }


We could start our Application by now but the values would not yet update. But as we want them to update like this:

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:
Java:
          /* Prepare the things, that will give
           * us the ability, to receive Information
           * about our GPS-Position. */

          this.setupForGPSAutoRefreshing();


So lets get into that heavily commented function:
Java:

     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);
           */

     }

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" Rolling Eyes :
"( registerReceiver(this.myIntentReceiver, this.myIntentFilter); ")

Java:
     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();
          }
     }


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).
Idea For further information about those onWhatEver()-Methods have a look here: Lifecycle of an Activity.

Java:
     /**
      * 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);
     }


You find the full code at the very very very end of this post.
Exclamation Congratulations, you've just created a auto-refreshing, gps-using Killer-Application Exclamation


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





Regards,
plusminus

_________________
Please remember, that this board is give & take Smile

| Android Development Community / Tutorials


Last edited by plusminus on Sat Jan 26, 2008 10:07 am; edited 1 time in total
Back to top
View user's profile Send private message Send e-mail Visit poster's website
aetmos
Junior Developer
Junior Developer


Joined: 18 Jan 2008
Posts: 21

PostPosted: Sat Jan 26, 2008 4:27 am    Post subject: Reply with quote

Quote:
As it is a pretty big function doing some advanced logic, we will split into peaces here.


s/peaces/pieces/g

Quote:
We chosse sorting the results by People.NAME in an ASCending order.


s/chosse/chose/g

Excellent tutorial, by the way. Thanks.

Tom

P.S. Not trying to be picky, just pointing out some typos...
Back to top
View user's profile Send private message
plusminus
Site Admin
Site Admin


Joined: 14 Nov 2007
Posts: 2439
Location: College Park, MD

PostPosted: Sat Jan 26, 2008 10:08 am    Post subject: Reply with quote

Hello aetmos,

awww no, those typos were really painful Razz
Definitely a result of less sleep in that time Wink

Regards,
plusminus

_________________
Please remember, that this board is give & take Smile

| Android Development Community / Tutorials
Back to top
View user's profile Send private message Send e-mail Visit poster's website
russoue
Freshman
Freshman


Joined: 16 Nov 2007
Posts: 6

PostPosted: Mon Jan 28, 2008 8:59 am    Post subject: Reply with quote

Thanks plusminus for the excellent tutorial! It was really helpful.
Back to top
View user's profile Send private message
coding_android
Moderator
Moderator


Joined: 05 May 2008
Posts: 48
Location: Germany

PostPosted: Thu May 08, 2008 2:16 pm    Post subject: Reply with quote

I'd like to say thank you for that great app. I'm diving into the Android experience and you're tutorials are very helpful. Smile
Back to top
View user's profile Send private message
buster
Junior Developer
Junior Developer


Joined: 22 Apr 2008
Posts: 20
Location: Berlin, Germany

PostPosted: Thu May 08, 2008 2:33 pm    Post subject: Reply with quote

As the Interface for the contacts seems to have changed (your screenshot which shows "Notes" looks completely different) i have a few questions:
1. when adding a contact, i noticed i can add custom fields to contacts. Obviously i could just create a "Gps" field with the location. But how do i access those custom fields within a cursor? Also, i'm wondering how this is working, as some contacts may have a gps field, others may not. From the SQL point of view there are two possibilites: Android adds a new column to the contacts which could result in many unused fields on other contacts. Or it could create a foreign table.. Anyways i'm wondering how i get this data.
The line of interest would be: "int notesColumn = c.getColumnIndex(People.NOTES); "
Obviously People.GPS doesnt work..

Thank you in advance (and for this great site!)

Sebastian
Back to top
View user's profile Send private message Visit poster's website
ca050306
Freshman
Freshman


Joined: 17 May 2008
Posts: 6

PostPosted: Wed May 21, 2008 6:16 pm    Post subject: Reply with quote

Hi Sebastian

I have the same issue, I created the custom fiel Notes but the function
int notesColumn = c.getColumnIndex(People.NOTES); return 1 and the string inside is null

It does not work as you wrote, if you find the solution let me know
BR
CA


buster wrote:
As the Interface for the contacts seems to have changed (your screenshot which shows "Notes" looks completely different) i have a few questions:
1. when adding a contact, i noticed i can add custom fields to contacts. Obviously i could just create a "Gps" field with the location. But how do i access those custom fields within a cursor? Also, i'm wondering how this is working, as some contacts may have a gps field, others may not. From the SQL point of view there are two possibilites: Android adds a new column to the contacts which could result in many unused fields on other contacts. Or it could create a foreign table.. Anyways i'm wondering how i get this data.
The line of interest would be: "int notesColumn = c.getColumnIndex(People.NOTES); "
Obviously People.GPS doesnt work..

Thank you in advance (and for this great site!)

Sebastian
Back to top
View user's profile Send private message
tannadevang
Freshman
Freshman


Joined: 11 May 2008
Posts: 2
Location: San Jose

PostPosted: Thu Jun 05, 2008 4:02 am    Post subject: adding contacts for FriendFinder application Reply with quote

Can you tell me where should the contacts be added for the FriendFinder application to work, i see this error .

Application Error:
com.android.friendfinder

An error has occured in com.android.friendfinder.
Unable to start activity
ComponenetInfo{com.android.friendfinder/com.andorid.friendfinder.FriendFinder}:
java.lang.SecurityException:
Permission
android.permission.READ_CONTACTS required for provider contacts.

i tried adding contacts name as the geo location but doesn thelp Sad


thanks,
devang
Back to top
View user's profile Send private message
buster
Junior Developer
Junior Developer


Joined: 22 Apr 2008
Posts: 20
Location: Berlin, Germany

PostPosted: Thu Jun 05, 2008 9:13 am    Post subject: Reply with quote

you need to add the proper permission in your manifest.xml as the error already says Wink
Open the Manifest.xml in Eclipse (you should see some good UI coming up, the Manifest Editor) and add that permission to your Activity.
Back to top
View user's profile Send private message Visit poster's website
cbraun75
Junior Developer
Junior Developer


Joined: 28 Jan 2008
Posts: 23
Location: Munich

PostPosted: Mon Jun 09, 2008 2:10 pm    Post subject: Reply with quote

Just have a Question for your Friend Nearby variable ...
I have the problem that i must now if this Locationpoint is the same location point i have in my Array. So I'm driving arround and if my position is in the radius of x meters or miles around a position which is in this array i'm doing something.

So I have
- me with a moving position and every x seconds i ask the array
- the array with a lot of saved positions
- a radius around the saved positions in the array (in this radius every request get true back)


Has anybody an idea in which way i can calculate this? How can i calculate the whole positions around the point which are accepted too?

Or some other ideas to solve it in a different way so that i don't need the array of fixed waypoints?
Back to top
View user's profile Send private message AIM Address
zak
Freshman
Freshman


Joined: 23 Sep 2008
Posts: 3

PostPosted: Fri Sep 26, 2008 9:49 am    Post subject: Reply with quote

hi, i have a question. In my android library there isn't com.google.android.maps.OverlayController class. where can i find it? thanks!
Back to top
View user's profile Send private message
ninor
Moderator
Moderator


Joined: 14 Aug 2008
Posts: 162
Location: Barcelona, Spain

PostPosted: Fri Sep 26, 2008 12:02 pm    Post subject: Reply with quote

zak wrote:
hi, i have a question. In my android library there isn't com.google.android.maps.OverlayController class. where can i find it? thanks!


This has changed to:
com.google.android.maps.Overlay
and
com.google.android.maps.OverlayItem

Search this site for some samples.

CU!
Back to top
View user's profile Send private message
Display posts from previous:   
       anddev.org - Android Development Community | Android Tutorials | Index -> Map Tutorials All times are GMT + 1 Hour
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


© 2007, Android Development Community
All rights reserved.
Powered by phpBB.