Tutorial:Hello Mapview, with GPS

From Notes

Jump to: navigation, search

This tutorial builds on the Android Mapview tutorial by incorporating:

  • Ways to interact with the map.
  • Location Listeners

Before trying this tutorial, work through the Mapview tutorial located here: [1].

Contents

Tapping on Android Markers

We want the application to take action when the user selects one of the markers on the map. This is called "Tapping" and require us to override the onTap() method.

In Eclipse, right click on the background of the HelloItemizedOverlay.java and select: Source > Override/Implement Methods. Select the onTap(int) method from the drop down menu. Then modify the default method definition so it looks like this:

/**
 * Called when the user clicks on one of the icons
 *   in the map. It uses a Toast to say hello.
 * @param pIndex is the Find's index in the ArrayList
 */
@Override
protected boolean onTap(int pIndex) {
   Toast.makeText(mContext, mOverlays.get(pIndex).getSnippet(),
        Toast.LENGTH_LONG).show();
   return true;
}

A Toast is a View widget that lets you display a short message to the user. The message will appear briefly in the current window and then disappear. As you can see from this code, a Toast needs a reference to the Context--i.e., to the current Activity. This is true of all views. So we need to define a Context field:

private Context mContext;

and a new constructor with a Context parameter so that we can pass the current Context when we create the Overlay:

public HelloItemizedOverlay(Context context, Drawable defaultMarker) {
    super(boundCenterBottom(defaultMarker));
    mContext = context;
}

Now we have to modify the onCreate() method in HelloMapView so that it uses this new constructor when it creates the Overlay:

itemizedOverlay = new HelloItemizedOverlay(this, drawable);

While we are at it, let's the modify the way we create the OverlayItems. We currently just pass in the overlay's location as a Point, leaving blank the Title and Snippet parameters:

OverlayItem overlayitem = new OverlayItem(point, "", "");

Let's fill in those two parameters in location-specific ways. For example, here's what we might do for the point in Mexico:

OverlayItem overlayitem = new OverlayItem(point, "Juan", "Ola!");

Time to run it. When you click on one of the overlay items, it should now say hello in that correct language.

Adding 'Me' to the Map

For this task we will be using a Location Service through the Internet, so add the following tags to your manifest file within the manifest tag:

 <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>	    

Location Service

The location service is provided either by a GPS satellite (GPS_PROVIDER) or by network towers (NETWORK_PROVIDER). This works as follows (a picture would be nice):

  1. The app registers with the service provider and requests to be updated with location updates.
  2. The app starts a background thread to receive messages from the service provider.
  3. When the service provide sends a message that location has changed, the app can update its location

Adding the Me Marker

Let's modify our application so that we can add a marker for the user at the user's current location. First let's add a menu to our application with a menu item for this task. We override the onCreateOptionsMenu() method, inherited from Activity:

	
/**
 * Creates menu items from a resource file.
 */
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.main, menu);
    return true;
}

We define the menu item in an XML file named main.xml in the res/menu folder. You will have to create this file and its folder in your project.

<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/add_new_marker" 
	android:title="@string/add_new_marker" 
	android:icon="@drawable/androidmarker">
</item>
</menu>

Note that you will have to add an entry to strings.xml in res/values/ for the menu's title.

To handle the menu we override the onOptionsItemSelected() method:

/**
 * Handles the creation of a new marker on the map.
 */
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.add_new_marker:
    	  Log.i(TAG, "<long, lat> = <" + mLongitude + "," + mLatitude + ">");
    	  GeoPoint point = new GeoPoint((int)(mLongitude * 1E6), (int)(mLatitude * 1E6));
    	  OverlayItem overlayitem = new OverlayItem(point, "Me", "Hello ");
          itemizedOverlay.addOverlay(overlayitem);
    	  mapOverlays.add(itemizedOverlay);
          return true;
    }
    return super.onOptionsItemSelected(item);
}

Note that in this case we set the point's longitude and latitude from the mLongitude and 'mLatitude fields, which are defined as follows:

private double mLongitude = 0;
private double mLatitude = 0;

Because they are defined as doubles--that's how GPS returns the values--we need to cast them into ints when we create the GeoPoint:

GeoPoint point = new GeoPoint((int)(mLongitude * 1E6), (int)(mLatitude * 1E6));

If you run the application now, it will add a new Marker at <0,0>, a point on the Equator, directly south of Greenwich, England.

Getting GPS Location Updates

To add GPS functionality to our application, we will implement the LocationListener interface. So modify the class declaration to this:

public class HelloMapView extends MapActivity implements LocationListener {

The LocationListener interface consists of the four methods, which should will be called automatically by the location service (either a NETWORK or a GPS service) on certain conditions. They should be implemented as shown:

/**
 * Invoked by the location service when phone's location changes.
 */
 public void onLocationChanged(Location newLocation) {
	setCurrentGpsLocation(newLocation);  	
 }
/**
 * Resets the GPS location whenever the provider is enabled.
 */
 public void onProviderEnabled(String provider) {
	setCurrentGpsLocation(null);  	
 }
/**
 * Resets the GPS location whenever the provider is disabled.
 */
 public void onProviderDisabled(String provider) {
	setCurrentGpsLocation(null);  	
 }
/**
 * Resets the GPS location whenever the provider status changes. We
 * don't care about the details.
 */
 public void onStatusChanged(String provider, int status, Bundle extras) {
	setCurrentGpsLocation(null);  	
 }

As you see, each method calls the setCurrentGpsLocation() method, passing either null or the newLocation if the location has changed. This method will get a pointer to the System's LOCATION_SERVICE and use the service to select a service provider and request periodic updates about the phone's location. In this case we will use the GPS_PROVIDER. Here's the code for this private method:

/**
 * Sends a message to the update handler with either the current location or 
 *  the last known location. 
 * @param location is either null or the current location
 */
 private void setCurrentGpsLocation(Location location) {
	String bestProvider = "";
	if (location == null) {
		mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
                mLocationManager.requestLocationUpdates(
		    LocationManager.GPS_PROVIDER, 30000, 0, this); // Every 30000 msecs	
		location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);				

	}
	try {
		mLongitude = location.getLongitude();
		mLatitude = location.getLatitude();
//		Log.i(TAG, "<long,lat> = <" + mLongitude + "," + mLatitude);
		Message msg = Message.obtain();
		msg.what = UPDATE_LOCATION;
		HelloMapView.this.updateHandler.sendMessage(msg);
	} catch (NullPointerException e) {
//		Log.i(TAG, "Null pointer exception " + mLongitude + "," + mLatitude);
	}
 }

Note that the try block attempts to use the location to set the mLongitude and mLatitude fields. Because location may not have been updated, you need to check for a NullPointerException here. Location updates are unpredictable.

This code will send an UPDATE_LOCATION message every time this code is executed. These messages are handled by a message handler object. We define this object and its handleMessage() among our field declarations as follows:

/**
 * Handles GPS updates.  
 * Source: Android tutorials
 * @see http://www.androidph.com/2009/02/app-10-beer-radar.html
 */
 Handler updateHandler = new Handler() {
	/** Gets called on every message that is received */
	// @Override
	public void handleMessage(Message msg) {
		switch (msg.what) {
		case UPDATE_LOCATION: {
			Log.i(TAG, "Updated location = " + mLatitude + " " + mLongitude);
			break;
		}
	        }
		super.handleMessage(msg);
	}
 };

Finally, because the location service will be constantly updating our application, we don't want to interrupt the user's interaction with these frequent updates. Therefore, we will define a separate thread to handle location updating:

/**
 * Handles location updates in the background.
 */
 class MyThreadRunner implements Runnable {
	// @Override
	public void run() {
		while (!Thread.currentThread().isInterrupted()) {
			Message m = Message.obtain();
			m.what = 0;
			HelloMapView.this.updateHandler.sendMessage(m);
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
		}
	}
 }

When run, the background thread will obtain whatever messages have been generated and invoke the updateHandler that we just defined to send the message. So the updateHandler both sends the message and handles the message.

Finally, we need to initialize all of this in our onCreate() method. To handle the initialization we define the following void method, which we simply call from onCreate(). It sets up the LocationManager and starts the background thread:

/**
 * Sets our location to the last known location and start 
 *  a separate thread to update GPS location.
 */
 private void initializeLocationAndStartGpsThread() {
	mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
	List<String> providers = mLocationManager.getProviders(ENABLED_ONLY);
	Log.i(TAG, "Enabled providers = " + providers.toString());
	String provider = mLocationManager.getBestProvider(new Criteria(),ENABLED_ONLY);
	Log.i(TAG, "Best provider = " + provider);

	setCurrentGpsLocation(null);   
	mThread = new Thread(new MyThreadRunner());
	mThread.start();
 }

The following statement should go at the end of the onCreate() method:

initializeLocationAndStartGpsThread();

Running on the Emulator

That's it. Try running it. If you are running on a phone, make sure you have GPS enabled and that you are outside, so that the GPS satellite signal can reach your phone. If you are not outside, you can change GPS_PROVIDER to NETWORK_PROVIDER in the setCurrentGpsLocation() method. Your location will then be updated from phone service towers.

If you are running on the emulator, you can simulate a GPS location and send updates to the emulator by using Emulator Control menu in the DDMS perspective in Eclipse.

Under the Wraps

It might be interesting to use Log statements to observe the running of this application in the LogCat view. Depending on where you place your Log statements, you might generate a lot of output.

Source Code

TBA

Additional Exercises

Distinguishing "Me" From Other Points

Right now the "Me" marker is no different from the markers that point to other people. To make sure you are distinguishable from others, there are a couple of things you should do.

First, find and add to res/drawable a new picture to represent you, say...a different colored android! File:Red-android.png Then, replace

OverlayItem overlayitem = new OverlayItem(point, "Me", "Hello ");
itemizedOverlay.addOverlay(overlayitem);
mMapOverlays.add(itemizedOverlay);

to:

Drawable myDrawable = this.getResources().getDrawable(R.drawable.android);
mMyItemizedOverlay = new HelloItemizedOverlay(this, myDrawable);
OverlayItem overlayitem = new OverlayItem(point, "Me", "Hello");
mMyItemizedOverlay.addOverlay(overlayitem);
mMapOverlays.add(mMyItemizedOverlay);

Make sure to create all necessary fields!

Removing Past Markers

If you send multiple locations to your emulator through eclipse, you will notice that multiple "Me" markers floating around. To make sure there is only one marker that marks where you are right now, insert these lines of code into your onOptionsItemSelected method before you instantiate a new ItemizedOverlay:

if (mMyItemizedOverlay != null)
    mMapOverlays.remove(mMyItemizedOverlay);

This will make sure there is only one "Me" marker floating around.

More on Maps

  1. Using Google Maps in Android, by Wei-ming Lee
  2. You are here: Using GPS and Google Maps in Android, by Wei-ming Lee.
Personal tools
NSF K-12