Google Maps for Android v2 sample

[Updated 4/27/2013] The sample now includes ability to change map types (satellite, terrain, hybrid, normal) via Action Bar actions. This whole project is in a Github project here: Android Map API v2 sample
Note: Running the sample code at the end of post requires a device with at least Android version 3.0. If you want to run on earlier OS versions, check out the updated sample: Get your MapFragment App Running on New and Old Devices

Well that was a fun couple of hours. Google Maps Android v2 came out not too long ago, and I thought I'd whip up a quick sample to see what's new with the version that uses Fragments.

There's a lot that's new. I'm not sure if MapView is now deprecated, but the new MapFragment returns a GoogleMap class, which is how it seems we interact with a map. I haven't scratched the surface of the new API yet.

I was able to code up a little Google Maps Android v2 sample from the documentation out there. Here's it running on the Nexus 4:

google maps android v2

Probably the hardest part coming from the old way, was the new way to register for the API key. The new system requires a number of steps including going to the Google API Console and registering your key and app's package. The Google Maps Android API v2 Getting Started has the steps to follow, and it worked out pretty well for me. Here's what I did:

  1. Install Google Play Services SDK
    • run Android from command line
    • scroll down to Extras
    • select Google Play services
    • Make sure you include this library in your project when you get to coding!
  2. Generate the debug key from a terminal prompt:
    • linux or osx: keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
    • windows: keytool -list -v -keystore "C:\Users\your_user_name\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
  3. Go to the Google APIs Console and Create a New Project
  4. Scroll down to "Google Maps Android API V2" and set the switch to On
  5. Paste in your SHA1 key (from Step 2 above), semicolon, application package name
    • ie, 7F:8B:BF:C1:6E:86:7F:5C:83:15:EA:BE:1F:B4:A3:C6:3D:70:51:22;com.codebybrian.mapsample
  6. Copy the Android API key
  7. Code! Put the key it gave you into your AndroidManifest.xml, in a <meta-data> block in the <application> section
  8. Very Important: Include the "google-play-services" library downloaded in Step 1 in your project! I didn't do this step and it took me a long time to figure this out.
  9. Super Important: I'm using IntelliJ (version 12), and I had to add the "google-play-services" directory as a dependent Module onto my main project (which involves making a new module out of it, then adding it as a dependency) AND I had to add the "google-play-services.jar" file as a dependent library also on my main project! It seems the jar file contains only the needed classes, but the rest of the directory (that I added as a Module) contains the resources.
  10. Create a little sample project, like I have below.

Below is the source to my sample project, and the whole android manifest, without my debug key in it. I want to come back to this sample as I have time, and upload it to github, and start adding a few more features to it.

package com.codebybrian.mapsample;

import android.app.Activity;
import android.app.FragmentManager;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

public class MyMapActivity extends Activity {
    private GoogleMap googleMap;
    private int mapType = GoogleMap.MAP_TYPE_NORMAL;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mymap);

        FragmentManager fragmentManager = getFragmentManager();
        MapFragment mapFragment =  (MapFragment) fragmentManager.findFragmentById(R.id.map);
        googleMap = mapFragment.getMap();

        LatLng sfLatLng = new LatLng(37.7750, -122.4183);
        googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
        googleMap.addMarker(new MarkerOptions()
                .position(sfLatLng)
                .title("San Francisco")
                .snippet("Population: 776733")
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));

        LatLng sLatLng = new LatLng(37.857236, -122.486916);
        googleMap.addMarker(new MarkerOptions()
                .position(sLatLng)
                .title("Sausalito")
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_VIOLET)));


        googleMap.getUiSettings().setCompassEnabled(true);
        googleMap.getUiSettings().setZoomControlsEnabled(true);
        googleMap.getUiSettings().setMyLocationButtonEnabled(true);


        LatLng cameraLatLng = sfLatLng;
        float cameraZoom = 10;

        if(savedInstanceState != null){
            mapType = savedInstanceState.getInt("map_type", GoogleMap.MAP_TYPE_NORMAL);

            double savedLat = savedInstanceState.getDouble("lat");
            double savedLng = savedInstanceState.getDouble("lng");
            cameraLatLng = new LatLng(savedLat, savedLng);

            cameraZoom = savedInstanceState.getFloat("zoom", 10);
        }

        googleMap.setMapType(mapType);
        googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(cameraLatLng, cameraZoom));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.map_styles_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);

        switch(item.getItemId()){
            case R.id.normal_map:
                mapType = GoogleMap.MAP_TYPE_NORMAL;
                break;

            case R.id.satellite_map:
                mapType = GoogleMap.MAP_TYPE_SATELLITE;
                break;

            case R.id.terrain_map:
                mapType = GoogleMap.MAP_TYPE_TERRAIN;
                break;

            case R.id.hybrid_map:
                mapType = GoogleMap.MAP_TYPE_HYBRID;
                break;
        }

        googleMap.setMapType(mapType);
        return true;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        // save the map type so when we change orientation, the mape type can be restored
        LatLng cameraLatLng = googleMap.getCameraPosition().target;
        float cameraZoom = googleMap.getCameraPosition().zoom;
        outState.putInt("map_type", mapType);
        outState.putDouble("lat", cameraLatLng.latitude);
        outState.putDouble("lng", cameraLatLng.longitude);
        outState.putFloat("zoom", cameraZoom);
    }
}

AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.codebybrian.mapsample"
          android:versionCode="1"
          android:versionName="1.0">

    <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="17"/>

    <permission
        android:name="com.codebybrian.mapsample.permission.MAPS_RECEIVE"
        android:protectionLevel="signature"/>


    <!--Required permissions-->

    <uses-permission android:name="com.codebybrian.mapsample.permission.MAPS_RECEIVE"/>

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

    <!--Used by the API to download map tiles from Google Maps servers: -->
    <uses-permission android:name="android.permission.INTERNET"/>

    <!--Allows the API to access Google web-based services: -->
    <uses-permission
    android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>

    <!--Allows the API to cache map tile data in the device's external storage area: -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


    <!--Optional permissions-->
	<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <!--Version 2 of the Google Maps Android API requires OpenGL ES version 2 -->
    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true"/>

    <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">

        <activity android:name=".MyMapActivity"
                  android:label="@string/app_name"
                >
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <meta-data
           android:name="com.google.android.maps.v2.API_KEY"
           android:value="your_key_goes_here"/>
        </application>

</manifest>
res/menu/map_styles_menu.xml :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/map_types"
          android:icon="@drawable/map_menu"
          android:title="Select map type"
          android:showAsAction="always">
        <menu>
            <item android:id="@+id/normal_map"
                  android:title="Normal map"/>
            <item android:id="@+id/satellite_map"
                  android:title="Satellite map"/>
            <item android:id="@+id/terrain_map"
                  android:title="Terrain map"/>
            <item android:id="@+id/hybrid_map"
                  android:title="Hybrid map"/>
        </menu>
    </item>
</menu>
mymap.xml:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/map"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          class="com.google.android.gms.maps.MapFragment"/>