Now this was a cluster-fuck of issues. I experienced error after error (after error) following the OFFICIAL documentation trying to get this to work.
Since starting development since Android SDK 1.0, the dev tools have really advanced at an amazing rate. However, this one is a huge setback in terms of productivity.
Searching for tutorials is another matter. They might be written for Maps API v2, but skim through little details which cause big errors. Either that or written for the deprecated v1 API.
I tinkered with MapActivity and MapView. I felt this was a necessary post as I've hit run many roadblocks, seen many errors;
Caused by: java.lang.RuntimeException: stub
java.lang.NoClassDefFoundError: com.google.android.maps.MapView
Caused by: android.app.Fragment$InstantiationException: Unable to instantiate fragment com.google.android.gms.maps.SupportMapFragment: make sure class name exists, is public, and has an empty constructor that is public
java.lang.NoClassDefFoundError: com.google.android.gms.R$styleable
Caused by: java.lang.ClassNotFoundException: com.google.android.gms.maps.SupportMapFragment
Well, I'm glad to say I've recently had a breakthrough with getting maps to work and I'm taking the chance to write about it now as the knowledge is still fresh and I still have the 30 tabs of sources open.
As a nice little extra, this solution will even work with API level 4+ as I've been told you need at least 7+ for it to work.
Downloading stuff
To begin, fire up Eclipse and let's start downloading! You can find the "Android SDK Manager" under the "Window" menu.
From the list of items, select:
- Extras > Google Play Services
- Android x.x (API #) > Google APIs (where # is the target API level of your project)
Once that's done, you may (or may not) have to restart your Eclipse. Don't worry, it'll prompt you.
Google Play Services project
- File menu (or right click project explorer) > Import
- Android > Existing Android code into workspace
- Click browse and find your Android SDK folder, then dig into "extras\google\google_play_services\libproject\google-play-services_lib". Yeah, I'm not kidding.
- If no projects appear, click "Refresh"
- Tick "google-play-services_lib"
- Select "Copy projects into workspace"
- Click Finish
Mine's disabled because I've already done it
Your project setup
As proof of concept, I've created a project that supports API level 4, compiled with Jelly Bean (API 16).
Once you have a project up and running, it's time for the "fun" XML-pasting part.
First up, linking the two projects.
- Right click on your project and select "Properties".
- Click on "Android" from the list on the left and then see "Library".
- Click "Add" and select "google-play-services_lib". If it does not appear, make sure that the Google Play Services library project is opened in Eclipse.
Secondly, your activity layout. I replaced the entire contents of the file with this.
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
Now for your Activity class.
Change this line:
public class MainActivity extends Activity {
to extend FragmentActivity:
import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {
Add in a GoogleMap instance and some initialisation code.
public class MainActivity extends FragmentActivity {
private GoogleMap gm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getBaseContext());
if (status == ConnectionResult.SUCCESS) {
SupportMapFragment supportMapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
gm = supportMapFragment.getMap();
}
else {
int requestCode = 10;
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(status, this, requestCode);
dialog.show();
}
}
}
And now for some messy stuff in AndroidManifest.xml.
Under the <manifest> tag, you'll need to access these permissions:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
- Internet and network state because, well, it needs the net to access maps.
- READ_GSERVICES to access Google services
- WRITE_EXTERNAL_STORAGE is to access the offline map cache
- glEsVersion isn't a permission but feature, so it'll restrict potential devices a little bit. Used when rendering the maps.
If you're planning on using the GPS to detect user coordinates, you'll also need these permissions:
- android.permission.ACCESS_COARSE_LOCATION: Allows the API to use WiFi or mobile cell data (or both) to determine the device's location.
- android.permission.ACCESS_FINE_LOCATION: Allows the API to use the Global Positioning System (GPS) to determine the device's location to within a very small area.
The following two are required custom permissions, so replace "com.example.testmaps" with your own package name.
<permission android:name="com.example.testmaps.permission.MAPS_RECEIVE" android:protectionLevel="signature"/>
<uses-permission android:name="com.example.testmaps.permission.MAPS_RECEIVE"/>
Now for the <application> tag.
<uses-library android:required="true" android:name="com.google.android.maps" />
<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="YOUR_KEY_HERE"/>
What's "YOUR_KEY_HERE"? We'll find out now =)
Generating an API key
Ugh, more steps. OK here's the plan. Google wants to keep track of your map usage so you don't abuse their services. At the same time, it has to be secure and tied to your app.
Before you go about generating a key, you have to get the SHA1 of your debug certificate first. Steps will be the same when you generate a key for the production certificate anyway.
To do that, open up a command prompt and change the directory to your Java install folder, and then switch to the "bin" directory.
For example, mine was "C:\Program Files (x86)\Java\jre7\bin".
To generate a SHA1 for debugging:
keytool -list -v -keystore %HOMEPATH%\.android\debug.keystore -alias androiddebugkey -storepass android -keypass android
For production, type this and follow the prompts:
keytool -list -v -keystore path_and_filename_of_keystore -alias your_alias_name
- When you get this screen, copy the SHA1 line to clipboard.
- Visit Google Code's APIs Console and create a new API project.
- Under "Services", scroll down to find "Google Maps Android API v2" and enable it
- Click on "API Access"
- Now click on "Create new Android key..."
- Paste in the SHA1 hash, BUT don't create it yet!
- You'll also need to append the app package name at the end, separated by a semicolon.
- So for my case, it's
WH:AT:EV:ER:SH:A1;com.example.testmaps
- NOW you can create the API key.
- Copy the API key into your app's AndroidManifest.xml file.
Problem is development and production app signatures differ, so you're going to have to generate keys (at least) twice for each app.
Finally, testing stage!
After all that bloody prep work, rebuild your project just to make sure the settings kick in!
Oh by the way, if you're getting these errors...
Installation error: INSTALL_FAILED_MISSING_SHARED_LIBRARY
Package com.example.testmaps requires unavailable shared library com.google.android.maps; failing!
Note that you can test this on the simulator. You have to change the "target" in the AVD settings to "Google APIs - API Level #" and I'm not sure if that involves wiping the device or not.
It's easiest to test on a real device. However if you're using Android x86 then you can simply add Google Play Services to your simulator. I have some notes below, but they're not 100% working due to OpenGL support.
Without further adieu, connect up your device and fire away! The first attempt will most likely fail. Maybe even the second.
Immediately I get these errors in logcat.
Google Maps Android API: Authorization failure. Please see https://developers.google.com/maps/documentation/android/start for how to correctly set up the map.
Google Maps Android API: Ensure that the following correspond to what is in the API Console: Package Name: com.example.testmaps, API Key: XXX, Certificate Fingerprint: ZZZ
Google Maps Android API: Failed to contact Google servers. Another attempt will be made when connectivity is established.
Google Maps Android API: Failed to load map. Error contacting Google servers. This is probably an authentication issue (but could be due to network errors).
As long as you've got the right API key and certificate fingerprint then you'll be fine by the 3rd attempt.
Here's a picture to prove that it works on the 3rd attempt without any changes.
And there you have it. Can you believe that it's finally done!?
I really wish this wasn't such a tedious or difficult setup, but that's the way it has to be. For now anyway.
We've done it! And the Despicable Me Minions go WILD! \o/
Android x86 simulator testing (work in progress)
If you're using Android x86, you may have some hope. Install GApps (Google Apps) from rootzwiki.com. Make sure you grab the version that's relevant to your Android build!
If you don't have system write access, you'll have to dig up the ISO and overwrite Android x86 with a re-install. Don't worry, your data and app stays the same.
Once that's ready:
- Download and extract gApps to your PC
- Using adb through the command prompt, push the files from "system" into your simulator /system
adb connect localhost
cd path/to/your/extracted/gapps_files
adb push system /system
- Reset your VM and it should lead you through a setup wizard
- Run your app and open the activity which displays the map.
- It should show you a link to install Google Play Services (You may need to set up an account for the device or log into your account)
- Install it via the Play Store which you should now have on the simulator (via gApps)
- Close up your app and try it again
And then you get an error:
Google Maps Android API: Google Maps Android API v2 only supports devices with OpenGL ES 2.0 and above
That's as far as I got. My VirtualBox/Android x86 setup doesn't support OpenGL ES 2.0 so I couldn't get the maps to render. Simulator works, but it's so slow. If you can find a way around it, feel free to let me know!
Sources
Possibly the best guide I've seen so far comes from a StackOverflow reply by Ramz to "NoClassDefFoundError at Google Play Services V2 library"
And in case you wanted some light reading.
- MapView Tutorial - Google Maps Android v1 API (Deprecated)
- SupportMapFragment - Google Maps Android API v2
- Google APIs Console
- MapView Documentation | Android Developers
- Signing Your Applications | Android Developers
- HowTo: ActionBarSherlock + MapFragment + ListFragment » Xavi Rigau
- android - Exception when using MapView - java.lang.RuntimeException: stub - Stack Overflow
- android - java.lang.RuntimeException - showing the Map activity after fb login - Stack Overflow
- Google Maps Android API v2 — Google Developers
- Google Maps Android API gives a NoClassDefFoundError - Stack Overflow
- Unable instantiate android.gms.maps.MapFragment - Stack Overflow
- android - Google Maps API v2 not working - Stack Overflow
- java.lang.NoClassDefFoundError: com.google.android.gms.R$styleable in android - Stack Overflow
- java.lang.noclassdeffounderror: com.google.android.gms.R$styleable - Stack Overflow
- xml - android : java.lang.NoClassDefFoundError: com.google.android.gms.R$styleable - Stack Overflow
- %HOMEPATH% and %HOMESHARE% Variables Are Resolved Incorrectly
- Google Apps - RootzWiki