Build your own Current Place picker for Android

Learn how to use Google Maps Platform and the Places SDK for Android to present your users with a list of Places to identify their current locations.

bd07a9ad2cb27a06.png

Prerequisites

  • Basic Java skills

What you'll do

  • Add a map to an Android app.
  • Use location permissions to geolocate the user.
  • Fetch Places near the user's current location.
  • Present likely Places to the user to identify their current location.

What you'll build

You build your Android app from scratch, but you can download the sample code for comparison when debugging. Download the sample code from GitHub, or, if you have Git set up for command-line use, enter the following:

git clone https://github.com/googlecodelabs/current-place-picker-android.git

If you run into any issues (code bugs, grammatical errors, unclear wording, or something else) as you work through this codelab, please report the issue via the Report a mistake link in the lower left corner of the codelab.

Before starting this codelab, you need to set up the following:

Android Studio

Download Android Studio from https://developer.android.com/studio.

If you already have Android Studio, make sure you have the latest version by clicking Android Studio > Check for Updates....

1f36bae83b64e33.png

This lab was written using Android Studio 3.4.

Android SDK

In Android Studio, you can configure your desired SDKs using the SDK Manager. This lab uses the Android Q SDK.

  1. From the Android Studio welcome screen, click Configure > SDK Manager.

d3fa03c269ec231c.png

  1. Select your desired SDK checkbox, then click Apply.

If you don't have the SDK yet, this will initiate downloading of the SDK to your machine.

884e0aa1314f70d.png

Google Play services

From the SDK manager, you also need to install Google Play services.

  1. Click SDK Tools tab and select the Google Play services checkbox.

Update if the status reads Update available.

ad6211fd78f3b629.png

To run the app, you can connect your own device or use the Android Emulator.

If using your own device, skip to Real device instructions: Update Google Play Services at the end of this page.

Add an emulator

  1. From the Android Studio welcome screen, click Configure > AVD Manager.

5dd2d14c9c56d3f9.png

This opens the Android Virtual Device Manager dialog.

  1. Click Create Virtual Device... to open a list of devices you can choose from.

2d44eada384f8b35.png

  1. Choose a device with the Play d5722488d80cd6be.png icon in the Play Store column and click Next.

e0248f1c6e85ab7c.png

You'll see a set of system images that you can install. If Q targeting Android 9.+ (Google Play) has the word Download next to it, click Download.

316d0d1efabd9f24.png

  1. Click Next to give your virtual device a name, then click Finish.

You return to the list of Your Virtual Devices.

  1. Click Start ba8adffe56d3b678.png next to your new device:

7605864ed27f77ea.png

After a few moments, the emulator opens.

Emulator instructions—update Google Play services

  1. Once the emulator launches, click ... in the navigation bar that appears**.**

2e1156e02643d018.png

This opens the Extended controls dialog.

  1. Click Google Play in the menu.

If an update is available, click Update.

5afd2686c5cad0e5.png

  1. Sign in to the emulator with a Google Account.

You can use your own or create a new account for free to keep your testing separate from your personal information.

Google Play then opens to Google Play services.

  1. Click Update to get the latest version of Google Play services.

f4bc067e80630b9c.png

If asked to complete your account setup and add a payment option, click Skip.

Set location in the emulator

  1. Once the emulator launches, type "maps" into the search bar on the home screen to pull up the Google Maps app icon.

2d996aadd53685a6.png

  1. Click the icon to launch.

You see a default map.

  1. At the bottom-right of the map, click Your Location c5b4e2fda57a7e71.png.

You're asked to give the phone permissions to use location.

f2b68044eabca151.png

  1. Click ... to open the Extended Controls menu.
  2. Click the Location tab.
  3. Enter a latitude and longitude.

Enter anything you like here, but make sure it's in an area with plenty of places.

(Use Latitude 20.7818 and Longitude -156.4624 for the town of Kihei on Maui in Hawaii to replicate the results from this codelab.)

  1. Click Send and the map updates with this location.

f9576b35218f4187.png

You're ready to run your app and test it with location.

Real device instructions—update Google Play services

If you're using a real Android device, then do the following:

  1. Use the search bar on the home screen to search for, and open, Google Play services.
  2. Click More Details.

If available, click Update.

ad16cdb975b5c3f7.png baf0379ef8a9c88c.png

  1. On the Android Studio welcome screen, select Start a new Android Studio project.
  2. On the Phone and Tablet tab, select Google Maps Activity.

c9c80aa8211a8761.png

The Configure your project dialog opens. Here is where you name your app and create the package based on your domain.

Here are the settings for an app called Current Place, which corresponds to the package com.google.codelab.currentplace.

37f5b93b94ee118c.png

  1. Choose Java as the language and select Use androidx. artifacts*.

Keep the defaults for the rest of the settings.

  1. Click Finish.

To access location permissions in Android, you need Google Location and Activity Recognition API from Google Play services. For more information on adding this and other Google Play services APIs, see Set Up Google Play Services.

Android Studio projects typically have two build.gradle files. One is for the overall project and one is for the app. If you have the Android Studio Project explorer in Android view, you see both of them in the Gradle Scripts folder. You need to edit the build.gradle (Module: app) file to add Google services.

f3043429cf719c47.png

  1. Add two lines to the dependencies section to add Google services for location and the Places API ( sample code in context).

build.gradle (Module: app)

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.google.codelab.currentplace"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'com.google.android.gms:play-services-maps:16.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

    implementation 'com.google.android.gms:play-services-location:16.0.0'
    implementation 'com.google.android.libraries.places:places:1.1.0'
}

This section explains how to authenticate your app to the Maps JavaScript API and Places API using your own API key.

  1. Go to Cloud Console.
  2. Create a new project.

56a42dfa7ac27a35.png

Enable billing

When you create a new project, you're prompted to choose which of your billing accounts you want to link to the project. If you have a billing account, that account is automatically linked. If you don't have a billing account, you must create one and enable billing for your project before you can use many Google Cloud features.

To create a new billing account, do the following:

  1. Click Navigation menu 13e088ce20968fe6.png and select Billing.
  2. Click Add billing account. (If this is your first billing account, click Create account.)
  3. Enter the name of the billing account and enter your billing information.

The options you see depend on the country of your billing address.

  1. Click Submit and enable billing.

By default, the person who creates the billing account is the billing administrator for the account.

For more information about verifying bank accounts and adding backup methods of payment, see Add, remove, or a payment method.

Get an API key

Follow these steps to enable the APIs used in this lab and get an API key:

  1. From the Navigation menu, select APIs & Services > Library.
  2. Click Maps SDK for Android tile, then click Enable.
  3. Repeat the first two steps to enable the Places API.
  4. From the Navigation menu, select APIs & Services > Credentials.
  5. Click Create Credentials and choose API key.
  6. Copy the API key displayed.
  7. Back in Android Studio, find google_maps_api.xml under Android > app > res > values.
  8. Replace YOUR_KEY_HERE with the API key you copied.

aa576e551a7a1009.png

Your app is now configured.

  1. In your project explorer, open the activity_maps.xml file in the Android > app > res > layout.

4e0d986480c57efa.png

  1. You'll see the basic UI open on the right of the screen, with tabs at the bottom allowing you to select the Design or Text editor for your layout. Select Text, and replace the entire contents of the layout file with this:

activity_maps.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:minHeight="?attr/actionBarSize"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:titleTextColor="@android:color/white"
        android:background="@color/colorPrimary" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <fragment
            android:id="@+id/map"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="349dp"
            tools:context=".MapsActivity" />

        <ListView
            android:id="@+id/listPlaces"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>

This will give you a user interface that looks like this:

1bf786808a4697ce.png

To give the user a button to click when they want to pick their current place, add an app bar with an icon that finds the user's current place and displays nearby likely places. It will look like this:

3a17c92b613a26c5.png

On a phone, only the icon is shown. On a tablet with more space, the text is also included.

Create the icon

  1. In the project explorer, click Android > app, then right-click the res folder and select New > Image Asset.

The Asset Studio opens.

  1. In the Icon Type menu, click Action Bar and Tab Icons.
  2. Name your asset ic_geolocate.
  3. Select Clip Art as the asset type**.**
  4. Click the graphic next to the Clip Art.

This opens the Select Icon window.

  1. Choose an icon.

You can use the search bar to find icons related to your intent.

  1. Search for location and pick a location-related icon.

The my location icon is the same as the one used in the Google Maps app when a user wants to snap the camera to their current location.

  1. Click OK > Next > Finish, and confirm there is a new folder called drawable that contains your new icon files.

b9e0196137ed18ae.png

Add string resources

  1. In the project explorer, click Android > app > res > values and open the strings.xml file.
  2. Add the following lines after <string name="title_activity_maps">Map</string>:

strings.xml

    <string name="action_geolocate">Pick Place</string>
    <string name="default_info_title">Default Location</string>
    <string name="default_info_snippet">No places found, because location permission is disabled.</string>

The first line is used in your app bar when there is space to include a text label next to the icon. The others are used for markers that you add to the map.

Now the code in the file looks like this:

<resources>
    <string name="app_name">Current Place</string>
    <string name="title_activity_maps">Map</string>
    <string name="action_geolocate">Pick Place</string>
    <string name="default_info_title">Default Location</string>
    <string name="default_info_snippet">No places found, because location permission is disabled.</string>
</resources>

Add the app bar

  1. In the project explorer, click Android > app, then right-click the res folder and select New > Directory to create a new subdirectory under app/src/main/res.
  2. Name the directory menu.
  3. Right-click the menu folder and select New > File.
  4. Name the file menu.xml.
  5. Paste in this code:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- "Locate me", should appear as action button if possible -->
    <item
        android:id="@+id/action_geolocate"
        android:icon="@drawable/ic_geolocate"
        android:title="@string/action_geolocate"
        app:showAsAction="always|withText" />

</menu>

Update the app bar style

  1. In the project explorer, expand the Android > app > res > values and open the file styles.xml inside.
  2. In the <style> tag, edit the parent property to be "Theme.AppCompat.NoActionBar".
  3. Note the name property, which you use in the next step.

styles.xml

<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">

Update the app theme in AndroidManifest.xml

  1. Click Android > app > manifests and open the AndroidManifest.xml file.
  2. Find the android:theme line and edit or confirm the value to be @style/AppTheme.

AndroidManifest.xml

   <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

You're now ready to begin coding!

  1. In your project explorer, find the MapsActivity.java file.

It's in the folder corresponding to the package that you created for your app in step 1.

8b0fa27d417f5f55.png

  1. Open the file and you're in the Java code editor.

Import the Places SDK and other dependencies

Add these lines at the top of MapsActivity.java, replacing the existing import statements.

They include the existing imports and add many more used in the code in this codelab.

MapsActivity.java

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.model.PlaceLikelihood;
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import com.google.android.libraries.places.api.net.PlacesClient;

import java.util.Arrays;
import java.util.List;

Update the class signature

The Places API uses AndroidX components for backward-compatible support, so you need to define it to extend the AppCompatActivity. It replaces the FragmentActivity extension that is defined by default for a maps activity.

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

Add class variables

Next, declare the various class variables used in different class methods. These include the UI elements and status codes. These should be just below the variable declaration for GoogleMap mMap.

    // New variables for Current Place picker
    private static final String TAG = "MapsActivity";
    ListView lstPlaces;
    private PlacesClient mPlacesClient;
    private FusedLocationProviderClient mFusedLocationProviderClient;

    // The geographical location where the device is currently located. That is, the last-known
    // location retrieved by the Fused Location Provider.
    private Location mLastKnownLocation;

    // A default location (Sydney, Australia) and default zoom to use when location permission is
    // not granted.
    private final LatLng mDefaultLocation = new LatLng(-33.8523341, 151.2106085);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    // Used for selecting the Current Place.
    private static final int M_MAX_ENTRIES = 5;
    private String[] mLikelyPlaceNames;
    private String[] mLikelyPlaceAddresses;
    private String[] mLikelyPlaceAttributions;
    private LatLng[] mLikelyPlaceLatLngs;

Update the onCreate method

You'll need to update the onCreate method to handle runtime user permissions for location services, setting up the UI elements and creating the Places API client.

Add the following lines of code regarding the action toolbar, views setup, and Places client to the end of the existing onCreate() method.

MapsActivity.java onCreate()

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        //
        // PASTE THE LINES BELOW THIS COMMENT
        //
        
        // Set up the action toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Set up the views
        lstPlaces = (ListView) findViewById(R.id.listPlaces);

        // Initialize the Places client
        String apiKey = getString(R.string.google_maps_key);
        Places.initialize(getApplicationContext(), apiKey);
        mPlacesClient = Places.createClient(this);
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
    }

Add code for your app bar menu

These two methods add the app bar menu (with a single item, the Pick Place icon) and handle the user's click on the icon.

Copy these two methods into your file after the onCreate method.

MapsActivity.java onCreateOptionsMenu() and onOptionsItemSelected()

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
           case R.id.action_geolocate:
                
                // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Present the current place picker
                // pickCurrentPlace();
                return true;

            default:
                // If we got here, the user's action was not recognized.
                // Invoke the superclass to handle it.
                return super.onOptionsItemSelected(item);

        }
    }

Test it

  1. From Android Studio, click Run or Run menu > Run ‘app'.

28bea91c68c36fb2.png

  1. You're asked to select your deployment target. The running emulator should appear on this list. Select it, and Android Studio deploys the app to the emulator for you.

f44658ca91f6f41a.png

After a few moments, the app launches. You see the map centered on Sydney, Australia, with the single button and unpopulated places list.

68eb8c70f4748350.png

The focus of the map doesn't move to the user's location unless you request permission to access the device's location.

Request location permissions after the map is ready

  1. Define a method called getLocationPermission that requests user permissions.

Paste this code below the onOptionsSelected method you just created.

MapsActivity.java getLocationPermission()

    private void getLocationPermission() {
        /*
         * Request location permission, so that we can get the location of the
         * device. The result of the permission request is handled by a callback,
         * onRequestPermissionsResult.
         */
        mLocationPermissionGranted = false;
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }
  1. Add two lines to the end of the existing onMapReady method to enable zoom controls and request location permissions from the user.

MapsActivity.java onMapReady()

   @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        // Add a marker in Sydney and move the camera
        LatLng sydney = new LatLng(-34, 151);
        mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));

        //
        // PASTE THE LINES BELOW THIS COMMENT
        //

        // Enable the zoom controls for the map
        mMap.getUiSettings().setZoomControlsEnabled(true);

        // Prompt the user for permission.
        getLocationPermission();

    }

Handle the result from requested permissions

When the user responds to the request permission dialog, this callback is called by Android.

Paste this code after the getLocationPermission() method:

MapsActivity.java onRequestPermissionsResult()

   /**
     * Handles the result of the request for location permissions
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
    }

When the user clicks Pick Place in the app bar, the app calls the method pickCurrentPlace(), which calls the getDeviceLocation() method you defined earlier. The getDeviceLocation method calls another method, getCurrentPlaceLikelihoods, after retrieving the latest device location.

Call the findCurrentPlace API and handle the response

getCurrentPlaceLikelihoods constructs a findCurrentPlaceRequest and calls the Places API findCurrentPlace task. If the task is successful, it returns a findCurrentPlaceResponse, which contains a list of placeLikelihood objects. Each of these have a number of properties, including the name and address of the place, and the likelihood probability that you are in that place (a double value from 0 to 1). This method handles the response by constructing lists of place details from the placeLikelihoods.

This code iterates through the five most-likely places and adds ones with a likelihood grater than 0 to a list which it then rendera. If you would like to display more or less than five, edit the M_MAX_ENTRIES constant.

Paste this code after the onMapReady method.

MapsActivity.java getCurrentPlaceLikelihoods()

   private void getCurrentPlaceLikelihoods() {
        // Use fields to define the data types to return.
        List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS,
                Place.Field.LAT_LNG);

        // Get the likely places - that is, the businesses and other points of interest that
        // are the best match for the device's current location.
        @SuppressWarnings("MissingPermission") final FindCurrentPlaceRequest request =
                FindCurrentPlaceRequest.builder(placeFields).build();
        Task<FindCurrentPlaceResponse> placeResponse = mPlacesClient.findCurrentPlace(request);
        placeResponse.addOnCompleteListener(this,
                new OnCompleteListener<FindCurrentPlaceResponse>() {
                    @Override
                    public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                        if (task.isSuccessful()) {
                            FindCurrentPlaceResponse response = task.getResult();
                            // Set the count, handling cases where less than 5 entries are returned.
                            int count;
                            if (response.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                                count = response.getPlaceLikelihoods().size();
                            } else {
                                count = M_MAX_ENTRIES;
                            }

                            int i = 0;
                            mLikelyPlaceNames = new String[count];
                            mLikelyPlaceAddresses = new String[count];
                            mLikelyPlaceAttributions = new String[count];
                            mLikelyPlaceLatLngs = new LatLng[count];

                            for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {
                                Place currPlace = placeLikelihood.getPlace();
                                mLikelyPlaceNames[i] = currPlace.getName();
                                mLikelyPlaceAddresses[i] = currPlace.getAddress();
                                mLikelyPlaceAttributions[i] = (currPlace.getAttributions() == null) ?
                                        null : TextUtils.join(" ", currPlace.getAttributions());
                                mLikelyPlaceLatLngs[i] = currPlace.getLatLng();

                                String currLatLng = (mLikelyPlaceLatLngs[i] == null) ?
                                        "" : mLikelyPlaceLatLngs[i].toString();

                                Log.i(TAG, String.format("Place " + currPlace.getName()
                                        + " has likelihood: " + placeLikelihood.getLikelihood()
                                        + " at " + currLatLng));

                                i++;
                                if (i > (count - 1)) {
                                    break;
                                }
                            }


                            // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                            // Populate the ListView
                            // fillPlacesList();
                        } else {
                            Exception exception = task.getException();
                            if (exception instanceof ApiException) {
                                ApiException apiException = (ApiException) exception;
                                Log.e(TAG, "Place not found: " + apiException.getStatusCode());
                            }
                        }
                    }
                });
    }

Move the map camera to the device's current location

If the user grants permission, the app fetches the user's latest location and moves the camera to center around that location.

If the user denies permission, the app simply moves the camera to the default location defined among the constants at the beginning of this page (in the sample code, it is Sydney, Australia).

Paste this code after the getPlaceLikelihoods() method:

MapsActivity.java getDeviceLocation()

    private void getDeviceLocation() {
        /*
         * Get the best and most recent location of the device, which may be null in rare
         * cases when a location is not available.
         */
        try {
            if (mLocationPermissionGranted) {
                Task<Location> locationResult = mFusedLocationProviderClient.getLastLocation();
                locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {
                    @Override
                    public void onComplete(@NonNull Task<Location> task) {
                        if (task.isSuccessful()) {
                            // Set the map's camera position to the current location of the device.
                            mLastKnownLocation = task.getResult();
                            Log.d(TAG, "Latitude: " + mLastKnownLocation.getLatitude());
                            Log.d(TAG, "Longitude: " + mLastKnownLocation.getLongitude());
                            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(mLastKnownLocation.getLatitude(),
                                            mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                        } else {
                            Log.d(TAG, "Current location is null. Using defaults.");
                            Log.e(TAG, "Exception: %s", task.getException());
                            mMap.moveCamera(CameraUpdateFactory
                                    .newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                        }

                       getCurrentPlaceLikelihoods();
                    }
                });
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

Check for location permissions when the user clicks Pick Place

When the user taps Pick Place, this method checks for location permissions and prompts the user for permission if it hasn't been granted.

If the user has granted permission, then the method calls getDeviceLocation to initiate the process of getting the current likely places.

  1. Add this method after getDeviceLocation():

MapsActivity.java pickCurrentPlace()

   private void pickCurrentPlace() {
        if (mMap == null) {
            return;
        }

        if (mLocationPermissionGranted) {
            getDeviceLocation();
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");

            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title(getString(R.string.default_info_title))
                    .position(mDefaultLocation)
                    .snippet(getString(R.string.default_info_snippet)));

            // Prompt the user for permission.
            getLocationPermission();
        }
    }
  1. Now that pickCurrentPlace is defined, find the line in onOptionsItemSelected() that calls pickCurrentPlace and uncomment it.

MapsActivity.java onOptionItemSelected()

           case R.id.action_geolocate:

                // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Present the Current Place picker
                pickCurrentPlace();
                return true;

Test it

If you run the app now and tap Pick Place, it should prompt for location permissions.

  • If you allow permission, that preference is saved and you won't get prompted. If you deny permission, you get prompted the next time you tap the button.
  • Although getPlaceLikelihoods has fetched the likely current places, the ListView does not display them yet. In Android Studio, you can click ⌘6 to check the logs in Logcat for statements tagged MapsActivity to verify that your new methods are working properly.
  • If you granted permission, the logs include a statement for Latitude: and a statement for Longitude: showing the detected location of the device. If you used Google Maps and the emulator's extended menu earlier to specify a location for the emulator, these statements show that location.
  • If the call to findCurrentPlace was successful, the logs include five statements printing the names and locations of the five most-likely places.

d9896a245b81bf3.png

Set up a handler for picked places

Let's think about what we want to happen when the user clicks an item in the ListView. To confirm the user's choice of which place they are currently, you can add a marker to the map at that place. If the user clicks that marker, an info window pops up displaying the place name and address.

Paste this click handler after the pickCurrentPlace method.

MapsActivity.java listClickedHandler

    private AdapterView.OnItemClickListener listClickedHandler = new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView parent, View v, int position, long id) {
            // position will give us the index of which place was selected in the array
            LatLng markerLatLng = mLikelyPlaceLatLngs[position];
            String markerSnippet = mLikelyPlaceAddresses[position];
            if (mLikelyPlaceAttributions[position] != null) {
                markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[position];
            }

            // Add a marker for the selected place, with an info window
            // showing information about that place.
            mMap.addMarker(new MarkerOptions()
                    .title(mLikelyPlaceNames[position])
                    .position(markerLatLng)
                    .snippet(markerSnippet));

           // Position the map's camera at the location of the marker.
            mMap.moveCamera(CameraUpdateFactory.newLatLng(markerLatLng));
        }
    };

Populate the ListView

Now that you have your list of most-likely places that the user is currently visiting, you can present those options to the user in the ListView. you can also set the ListView click listener to use the click handler you just defined.

Paste this method after the click handler:

MapsActivity.java fillPlacesList()

    private void fillPlacesList() {
        // Set up an ArrayAdapter to convert likely places into TextViews to populate the ListView
        ArrayAdapter<String> placesAdapter =
                new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mLikelyPlaceNames);
        lstPlaces.setAdapter(placesAdapter);
        lstPlaces.setOnItemClickListener(listClickedHandler);
    }

Now that fillPlacesList is defined, find the line toward the end of findPlaceLikelihoods that calls fillPlacesList and uncomment it.

MapsActivity.java fillPlaceLikelihoods()

               // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Populate the ListView
                fillPlacesList();

That is all the code needed for the Current Place picker!

Test picking a place

  1. Run the app again.

This time when you tap Pick Place, the app populates the list with named places close to the location. Close to this location on Maui are places like Ululani's Hawaiian Shave Ice and Sugar Beach Bake Shop. Because the several places are very close to the location coordinates, this is a list of likely places you may be in.

  1. Click on a place name in the ListView.

You should see a marker added to the map.

  1. Tap the marker.

You can see Place Details.

e52303cc0de6a513.png 864c74342fb52a01.png

Test a different location

If you want to change your location and are using the emulator, the device location doesn't automatically update when you update the location coordinates in the emulator's extended menu.

To get around this, follow these steps to use the native Google Maps app to force updates to the emulator's location:

  1. Open Google Maps.
  2. Tap ... > Location to change the latitude and longitude to new coordinates, then tap Send.
  3. For example, you can use Latitude: 49.2768 and Longitude: -123.1142 to set the location to downtown Vancouver, Canada.
  4. Verify that Google Maps has recentered on your new coordinates. You may need to tap the My Location button in the Google Maps app to request the recentering.
  5. Return to your Current Place app and tap Pick Place to get the map on the new coordinates and see a new list of likely current places.

9adb99d1ce25c184.png

And that's it! You built a simple app that checks for the places at the current location and gives you a likelihood of which ones you're at. Enjoy!

Now go ahead and the run app with the modifications you made to complete this bonus step!

To prevent theft of your API key, you need to secure it so only your Android app can use the key. If left unrestricted, anyone with your key could use it to call Google Maps Platform APIs and cause you to get billed.

Get your SHA-1 certificate

You need this later when you restrict your API keys. The following is a set of instructions for getting your debug certificate.

For Linux or macOS, open a terminal window and enter the following:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

For Windows Vista and Windows 7, run the following command:

keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android

You should see output similar to this:

Alias name: androiddebugkey
Creation date: Jan 01, 2013
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, O=Android, C=US
Issuer: CN=Android Debug, O=Android, C=US
Serial number: 4aa9b300
Valid from: Mon Jan 01 08:04:04 UTC 2013 until: Mon Jan 01 18:04:04 PST 2033
Certificate fingerprints:
     MD5:  AE:9F:95:D0:A6:86:89:BC:A8:70:BA:34:FF:6A:AC:F9
     SHA1: BB:0D:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:75
     Signature algorithm name: SHA1withRSA
     Version: 3

The line that begins SHA1 contains the certificate's SHA-1 fingerprint. The fingerprint is the sequence of 20 double-digit hexadecimal numbers separated by colons.

When you are ready to release an app, use the instructions in this documentation to retrieve your release certificate.

Add restrictions to your API key

  1. In Cloud Console, navigate to APIs & Services > Credentials.

The key you used for this app should be listed under API Keys.

  1. Click 6454a04865d551e6.png to edit the key settings.

316b052c621ee91c.png

  1. On the API key page, after Key restrictions, set the Application restrictions by doing the following:
  2. Select Android apps and follow the instructions.
  3. Click Add an item.
  4. Enter your package name and SHA-1 certificate fingerprint (retrieved in the previous section).

For example:

com.google.codelab.currentplace
BB:0D:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:75s
  1. For further protection, set the API restrictions by doing the following.
  2. After API restrictions, choose Restrict key.
  3. Select the Maps SDK for Android and Places API.
  4. Click Done and Save.

You built a simple app that checks for the most-likely places at the current location and adds a marker to the map for the place the user selects.

Learn more