Google Maps For Android: Basics To Advanced Features
Last Updated: September 21, 2015
Google Maps is one of the most awesome applications that Google makes available for us. Their integration in Android apps is a common desire of a lot of developers, but fortunately it’s not so difficult.
In this tutorial, you will learn how to do it and the example will show you important concepts from basic configuration to advanced use cases.
We are going to build a “distance calculator” using Google Maps. The approach is totally visual. We choose two points on the Map: the first one moving a Marker, the second one tapping the display.
The app draws a red line between these points, calculates their distance and retrieves corresponding street addresses via geocoding. At the end, an alert dialog notifies the user of the results.
The following figure shows the app in action:
The case we are treating is interesting as it involves several features beyond the foundations of Google Maps in Android: markers, drawing on the map, distance calculation by Location objects, handling events on Map and geocoding.
The code to write is not so much because Java classes in Google Play Services SDK make our job easier.
Let’s start.
Google Play Services and Maps: preparing the project
To uniform interaction between apps and its own services (including Maps, Drive, Google Plus and so on), Google provided Google Play Services, a software package integrated in Android OS and constantly updated.
An app that uses Google Maps API requires the integration of Google Play Services SDK in the project.
The first step is installing Google Play Services library in our app. The Android SDK Manager can help us. This tutorial is based on Android Studio, the official development tool.
In the Android SDK Manager GUI (we can open it selecting the “SDK Manager” item from Tools > Android sub-menu), we have to download two elements: “Google Play Services” and “Google Repository”.
At the end of download, you can integrate it in the project by using Gradle directives:
dependencies { … … compile 'com.google.android.gms:play-services:7.3.0' }
I indicated 7.3.0 as version number according to my installation. Obviously, it has to be personalized.
However, this is the complete Gradle build file:
apply plugin: 'com.android.application' android { compileSdkVersion 21 buildToolsVersion "22.0.1" defaultConfig { applicationId "com.androidbegin.googlemapstutorial" minSdkVersion 14 targetSdkVersion 21 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.1.1' compile 'com.google.android.gms:play-services:7.3.0' }
Creating a Google API Project
After installing the library, you have to create a Google project on the Google Developers Console (https://console.developers.google.com). To access it you only need a Google account: your Gmail credentials will be sufficient.
This allows you to create an API key that is required to permit your app to download information’s from Google Maps service.
First of all, you need a Google API Project. After successful login, select an existing project or create a new one clicking the “Create project” button.
The dialog window that appears will ask you a Project Name and a Project ID. You can choose both them.
In the sidebar on the left, select “API & Auth” > APIs. Search “Google Maps Android API v2” and enable them.
Now you can create your access key.
Select “API & Auth” > Credentials and click the button “Create a new key”. We need a Public access key, but it’s important to remember that it has to be an “Android key”.
You will be asked for a code that is the result of the concatenation of two parts separated by a semicolon: a SHA-1 code and your app’s package name.
The SHA-1 code is your app fingerprint. You can obtain it using key-tool, but the command is different in every operating system.
For Windows:
keytool -list -v -keystore “%USERPROFILE%.androiddebug.keystore” -alias androiddebugkey -storepass android -keypass android
For Linux or Mac:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
These commands generate a debug key. You have to search a SHA1 key in the amount of output.
In my project, SHA1 fingerprint and app’s package name look like this:
17:5F:A3:CA:67:94:E1:AA:E4:02:11:82:9B:3D:8A:53:3A:B2:8D:12;com.androidbegin.googlemapstutorial
After creation you can see an API key like this:
AIzaSy62NbSRLQScJa_5sAWBYM_fGA0G4eAYCeo
The key will be copied and pasted in your project manifest file.
The configuration: the AndroidManifest.xml file
First of all, we have to configure the application.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.androidbegin.googlemapstutorial" > <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="@string/google_maps_key" /> <activity android:name=".MapsActivity" android:label="@string/title_activity_maps" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
The AndroidManifest.xml contains fundamental elements so that GoogleMap class we use can get information’s from remote servers.
The required permissions are:
- android.permission.INTERNET;
- android.permission.ACCESS_NETWORK_STATE;
- android.permission.WRITE_EXTERNAL_STORAGE.
They allow the app to access the network and save data for cache on external storage.
Other two permissions are not indispensable, but they can be useful if the app accesses user location:
ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION.
Then, we add two meta-data nodes as a child of the <application> element. The first one sets the API KEY we created in our Google Project. The second one declares the version of Google Play Services.
Values within them are referred to files in two /res/values sub-folders:
google_maps_api.xml:
<resources> <string name="google_maps_key">AIzaSy62NbSRLQScJa_5sAWBYM_fGA0G4eAYCeo</string> </resources>
and version.xml:
<resources> <integer name="google_play_services_version">7327000</integer> </resources>
The Activity
The only element we need in the layout is a fragment containing the Map that will be provided from the SupportMapFragment class:
<fragment xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/map" android:name="com.google.android.gms.maps.SupportMapFragment" />
The code in the Activity class perform three main tasks:
- configuration of the UI and check of the availability of a Map;
- configuration of the Map included the Marker and the listener that handles its drag-and-drop events;
- Map click listener: it activates the functionality of the distance calculation.
public class MapsActivity extends FragmentActivity { // the Google Map object private GoogleMap mMap; // LatLng objects store a pair of terrestrial coordinates (latitude and longitude) // STARTING_MARKER_POSITION first values are the coordinates of the Colosseo in Rome (Italy) private static LatLng STARTING_MARKER_POSITION =new LatLng(41.892950, 12.494456); /* distanceFrom indicates the starting point to calculate the distance from. It's initialized with STARTING_MARKER_POSITION */ private LatLng distanceFrom= STARTING_MARKER_POSITION; // line will be drawn at the click event private Polyline line=null; // A Geocoder can transform a pair of latitude/longitude into a street address and viceversa. // We'll use it in the listener private static Geocoder geocoder=null; private GoogleMap.OnMapClickListener clickListener=new GoogleMap.OnMapClickListener() { @Override public void onMapClick(final LatLng pos) { // this method is called when the user taps the map // if a line already appears, it's removed if (line!=null) line.remove(); // a new line is created line = mMap.addPolyline(new PolylineOptions() .add(distanceFrom, pos) .width(5) // width of the line .color(Color.RED)); // line color // call the converter object for geocoding invocation and distance calculation new AddressConverter().execute(distanceFrom, pos); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // we set the layout for the Activity setContentView(R.layout.activity_maps); // the geocoder is instantiated for the first time geocoder=new Geocoder(this); // if there isn't a map, it will be created setUpMapIfNeeded(); } @Override protected void onResume() { super.onResume(); // the availability of the GoogleMap will be checked before the Activity starts interacting with the user setUpMapIfNeeded(); } private void setUpMapIfNeeded() { // the map is created only it has not been initialized if (mMap == null) { // the map is located in the layout mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap(); // if a map exists, we proceed with initialization if (mMap != null) { setUpMap(); } } } // Now it's time to configure the map. We can add markers, shapes, event handlers and so on private void setUpMap() { // the camera will be positioned according to the new coordinates mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(STARTING_MARKER_POSITION, 16)); // we choose the type of the map: Satellite in this case mMap.setMapType(GoogleMap.MAP_TYPE_SATELLITE); // markerOptions describes the marker we want to place MarkerOptions markerOptions=new MarkerOptions() .position(STARTING_MARKER_POSITION) .draggable(true); // the marker has to be draggable as we'll move it // the marker is rendered on the map mMap.addMarker(markerOptions); // we define the object to invoke when the marker is dragged mMap.setOnMarkerDragListener(new GoogleMap.OnMarkerDragListener() { @Override public void onMarkerDragStart(Marker arg0) { // this method is called when the drag starts // the operation we need is the cancellation of a preexisting line if (line!=null) line.remove(); } @Override public void onMarkerDragEnd(final Marker pos) { // we get the final position of the marker distanceFrom=pos.getPosition(); } @Override public void onMarkerDrag(Marker arg0) { // operations performed during the movement. Nothing to do } }); // the callback to invoke is set mMap.setOnMapClickListener(clickListener); } // we want to know which address corresponds to this location // we use AsyncTask to perform slower operations on a separate thread private class AddressConverter extends AsyncTask<LatLng,Void,String> { // The ProgressDialog window we'll show during the calculation private ProgressDialog progress=null; // this method is called before the background job starts. It works on the main thread @Override protected void onPreExecute() { // ProgressDialog is shown progress= ProgressDialog.show(MapsActivity.this,"Distance calculator","We are calcultating the distance...", true,false); } // this method works on a separate thread // it performs geocoding operations to retrieve the address of the points and calculates the distance in meters between them @Override protected String doInBackground(LatLng... params) { float[] distance=new float[1]; try { // the Location class contains what we need to calculate distances Location.distanceBetween(params[0].latitude,params[0].longitude,params[1].latitude,params[1].longitude,distance); // geocoding operations List<Address> fromResult=geocoder.getFromLocation(params[0].latitude,params[0].longitude,1); List<Address> toResult=geocoder.getFromLocation(params[1].latitude,params[1].longitude,1); // the message informs the user about the distance from the marker to the point selected with the click // if we have got both the addresses, we use them to compose the message, otherwise we show only the distance if (fromResult.size()>0 && toResult.size()>0) { return "The distance between " + getAddressDescription(fromResult.get(0)) + " and " + getAddressDescription(toResult.get(0)) + " is " + Math.round(distance[0]) + " meters"; } else return "The distance is " + Math.round(distance[0]) + " meters"; } catch (IOException e) { return "The distance is " + Math.round(distance[0]) + " meters"; } } @Override protected void onPostExecute(String message) { if (progress!=null) progress.dismiss(); // The builder of the window is instantiated AlertDialog.Builder builder=new AlertDialog.Builder(MapsActivity.this); builder.setTitle("Distance"); builder.setMessage(message); // the Alert dialog appears builder.show(); } } // this method only formats the message with addresses private String getAddressDescription(Address a) { String city=a.getLocality(); String address=a.getAddressLine(0); return "'"+address+"' ("+city+")"; } }
The onCreate and onResume methods set the layout and initialize a Geocoder object we’ll use to convert geographical coordinates into street addresses.
We store a reference to a GoogleMap object as a private member of the Activity. This reference is retrieved in the setUpMapIfNeeded method call from onResume.
The most important portion of configuration is performed in setUpMap. After calling it, we know a GoogleMap object is available.We move the camera that indicates what part of the world our Map has to show.
The position we start from is described in a LatLng object, referenced in the code as STARTING_MARKER_POSITION.
The programmer can choose the type of the map. We prefer a satellite map that shows details of the city. STARTING_MARKER_POSITION is set to Colosseum coordinates, one of the most important monument of the ancient Rome.
To create a Marker, we have to instantiate a MarkerOptions object. At least, it has to get the marker position as a LatLng object. It’s important that the marker is draggable for our purposes
The OnMarkerDragListener requires the override of three methods:
- onMarkerDragStart: it’s called when the drag movement starts. We need only to delete a line if a calculation has just been executed;
- onMarkerDrag: called repeatedly during the movement. We don’t need it;
- onMarkerDragEnd: it notifies the end of the drag movement. We store final Marker position into distanceFrom variable.
The nested class: geocoding operations
There’s a nested class within MapsActivity, AddressConverter. It extends AsyncTask as we use it to perform geoconding operations. The Geocoder needs to communicate with external servers and this can cause some delay.
AsyncTask definition contains an abstract method, doInBackground, that executes code on a separate thead to not affect UI performances.
On the other hand, onPreExecute and onPostExecute methods work on main thread so they can update the user interface.
The onPreExecute method shows a ProgressDialog that will disappear at the completion of background job.
In doInBackground, we calculate the distance between the points and perform geocoding operations using the Geocoder object. Then, we prepare the string for the message that will be showed in the AlertDialog.
The onPostExecute method dismisses the ProgressDialog as the background job has terminated and shows the AlertDialog that contains computation results for the user.
Source Code
[purchase_link id=”8048″ text=”Purchase to Download Source Code” style=”button” color=”green”]