Crea tu propio selector de Current Place para Android (Java)

1. Antes de comenzar

Aprende a usar Google Maps Platform y el SDK de Places para Android a fin de presentarles a tus usuarios una lista de lugares para identificar sus ubicaciones actuales.

bd07a9ad2cb27a06.png

Requisitos previos

  • Habilidades básicas de Java

Actividades

  • Agregarás un mapa a una app para Android.
  • Usarás permisos de ubicación para localizar al usuario según su ubicación geográfica.
  • Obtendrás lugares cerca de la ubicación actual del usuario.
  • Presentarás lugares probables al usuario para identificar su ubicación actual.

Qué crearás

Crearás tu app para Android desde cero, pero puedes descargar el código de muestra para compararlo durante la depuración. Descarga el código de muestra de GitHub. Si tienes configurado Git para usarlo en la línea de comandos, ingresa lo siguiente:

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

Si encuentras algún problema (errores de código, errores gramaticales, palabras poco claras o algún otro inconveniente) mientras trabajas en este codelab, informa el problema a través del vínculo Informar un error, que se encuentra en la esquina inferior izquierda del codelab.

2. Comenzar

Antes de comenzar este codelab, debes configurar lo siguiente:

Android Studio

Descarga Android Studio de https://developer.android.com/studio.

Si ya tienes Android Studio, asegúrate de que sea la última versión. Para ello, haz clic en Android Studio > Check for Updates….

1f36bae83b64e33.png

Para este lab, se utilizó Android Studio 3.4.

SDK de Android

En Android Studio, puedes configurar los SDK que desees usando SDK Manager. En este lab, se usa el SDK de Android Q.

  1. En la pantalla de bienvenida de Android Studio, haz clic en Configure > SDK Manager.

d3fa03c269ec231c.png

  1. Selecciona la casilla de verificación del SDK que desees y haz clic en Apply.

Si aún no tienes ese SDK, se iniciará la descarga de este en tu máquina.

884e0aa1314f70d.png

Servicios de Google Play

También necesitas instalar los Servicios de Google Play desde SDK Manager.

  1. Haz clic en la pestaña SDK Tools y selecciona la casilla de verificación Google Play Services.

Si el estado dice Update available, instala la actualización.

ad6211fd78f3b629.png

3. Prepara el emulador

Para ejecutar la app, puedes conectar tu propio dispositivo o usar Android Emulator.

Si usas tu propio dispositivo, ve directamente a Instrucciones para dispositivos reales: Actualiza los Servicios de Google Play al final de esta página.

Agrega un emulador

  1. En la pantalla de bienvenida de Android Studio, haz clic en Configure > AVD Manager.

5dd2d14c9c56d3f9.png

Se abrirá el diálogo Android Virtual Device Manager.

  1. Haz clic en Create Virtual Device… para abrir la lista de dispositivos que puedes elegir.

2d44eada384f8b35.png

  1. Selecciona un dispositivo que tenga el ícono de Play d5722488d80cd6be.png en la columna Play Store y haz clic en Next.

e0248f1c6e85ab7c.png

Verás un conjunto de imágenes del sistema que puedes instalar. Si la versión de Q orientada a Android 9 o una versión posterior (Google Play), tiene la palabra Download junto a ella, haz clic en esa palabra.

316d0d1efabd9f24.png

  1. Haz clic en Next para asignarle un nombre a tu dispositivo virtual y, luego, haz clic en Finish.

Vuelve a mostrarse la lista Your Virtual Devices.

  1. Haz clic en Start ba8adffe56d3b678.png junto al nuevo dispositivo:

7605864ed27f77ea.png

Después de unos segundos, se abrirá el emulador.

Instrucciones del emulador: Actualiza los Servicios de Google Play

  1. Una vez que se inicie el emulador, haz clic en en la barra de navegación que aparece**.**

2e1156e02643d018.png

Se abrirá el diálogo Extended controls.

  1. Haz clic en Google Play en el menú.

Si hay una actualización disponible, haz clic en Update.

5afd2686c5cad0e5.png

  1. Accede al emulador con una Cuenta de Google.

Puedes usar tu propia cuenta o crear una nueva, de forma gratuita a fin de mantener las pruebas separadas de tu información personal.

A continuación, se abre Google Play en los Servicios de Google Play.

  1. Haz clic en Actualizar (Update) para obtener la versión más reciente de los Servicios de Google Play.

f4bc067e80630b9c.png

Si se te solicita completar la configuración de tu cuenta y agregar una opción de pago, haz clic en Omitir.

Configura la ubicación en el emulador

  1. Una vez que se inicie el emulador, escribe "maps" en la barra de búsqueda de la pantalla principal para acceder al ícono de la app de Google Maps.

2d996aadd53685a6.png

  1. Haz clic en el ícono para abrir la app.

Verás un mapa predeterminado.

  1. En la parte inferior derecha del mapa, haz clic en Tu ubicación c5b4e2fda57a7e71.png.

Se te pedirá que le otorgues permiso al teléfono para usar la ubicación.

f2b68044eabca151.png

  1. Haz clic en para abrir el menú Extended Controls.
  2. Haz clic en la pestaña Location.
  3. Ingresa una latitud y una longitud.

Escribe algo que quieras aquí, pero asegúrate de que se trate de un área con muchos lugares.

(Usa Latitude 20.7818 y Longitude -156.4624 para la ciudad de Kihei en Maui, Hawái, a fin de replicar los resultados de este codelab).

  1. Haz clic en Send, y el mapa se actualizará con esta ubicación.

f9576b35218f4187.png

Ya está todo listo para que ejecutes tu app y la pruebes con la ubicación.

Instrucciones para dispositivos reales: Actualiza los Servicios de Google Play

Si usas un dispositivo Android real, haz lo siguiente:

  1. Utiliza la barra de búsqueda de la pantalla principal para buscar y abrir los Servicios de Google Play (Google Play services).
  2. Haz clic en Más detalles.

Si está disponible, haz clic en Actualizar (Update).

ad16cdb975b5c3f7.png baf0379ef8a9c88c.png

4. Crea el shell de la app con una actividad en Google Maps

  1. En la pantalla de bienvenida de Android Studio, selecciona Start a new Android Studio project.
  2. En la pestaña Phone and Tablet, selecciona Google Maps Activity.

c9c80aa8211a8761.png

Se abre el diálogo Configure your project. Aquí le asignas un nombre a tu app y creas el paquete basado en tu dominio.

Esta es la configuración de una app llamada Current Place, que corresponde al paquete com.google.codelab.currentplace.

37f5b93b94ee118c.png

  1. Elige Java como lenguaje y selecciona Use androidx. artifacts.*

Mantén los valores predeterminados para el resto de la configuración.

  1. Haz clic en Finish.

5. Agrega las dependencias de los Servicios de Google al archivo de compilación de Gradle

Para acceder a los permisos de ubicación en Android, necesitas las API de Google Location y Activity Recognition de los Servicios de Google Play. Para obtener más información sobre cómo agregar esta y otras API de los Servicios de Google Play, consulta Cómo configurar los Servicios de Google Play.

Los proyectos de Android Studio suelen tener dos archivos build.gradle. Uno es para el proyecto en general y el otro es para la app. Si tienes el explorador de proyectos de Android Studio en la vista Android, verás ambos en la carpeta Gradle Scripts. Debes editar el archivo build.gradle (Module: app) para agregar los servicios de Google.

f3043429cf719c47.png

  1. Agrega dos líneas a la sección dependencies para agregar los servicios de Google correspondientes a la ubicación y la API de Places (código de muestra en contexto).

build.gradle (módulo: app)

plugins {
  id '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'
}

6. Habilita las API de Google Maps Platform y obtén una clave de API

Para el paso siguiente, debes habilitar el SDK de Maps para Android y la API de Places.

Configura Google Maps Platform

Si todavía no tienes una cuenta de Google Cloud Platform y un proyecto con la facturación habilitada, consulta la guía Cómo comenzar a utilizar Google Maps Platform para crear una cuenta de facturación y un proyecto.

  1. En Cloud Console, haz clic en el menú desplegable del proyecto y selecciona el proyecto que deseas usar para este codelab.

  1. Habilita las API y los SDK de Google Maps Platform necesarios para este codelab en Google Cloud Marketplace. Para hacerlo, sigue los pasos que se indican en este video o esta documentación.
  2. Genera una clave de API en la página Credenciales de Cloud Console. Puedes seguir los pasos que se indican en este video o esta documentación. Todas las solicitudes a Google Maps Platform requieren una clave de API.

Copia la clave de API que acabas de crear. Vuelve a Android Studio y busca el archivo google_maps_api.xml en Android > app > res > values.

Reemplaza YOUR_KEY_HERE por la clave de API que copiaste.

aa576e551a7a1009.png

Tu app ya está configurada.

7. Edita el archivo de diseño

  1. En el explorador de tu proyecto, abre el archivo activity_maps.xml en Android > app > res > layout.

4e0d986480c57efa.png

  1. La IU básica se encuentra a la derecha de la pantalla y tiene pestañas en la parte inferior que te permiten seleccionar el editor de diseño o texto para tu diseño. Selecciona Text y reemplaza todo el contenido del archivo de diseño por lo siguiente:

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>

Verás una interfaz de usuario similar a la siguiente:

1bf786808a4697ce.png

8. Configura la barra de la aplicación

Si quieres darle al usuario un botón que pueda hacer clic para seleccionar su lugar actual, agrega una barra de la aplicación con un ícono que busque el lugar actual del usuario y muestre los lugares cercanos probables. Se verá de la siguiente forma:

3a17c92b613a26c5.png

En un teléfono, solo se muestra el ícono. En una tablet con más espacio, también se incluye el texto.

Crea el ícono

  1. En el explorador del proyecto, haz clic en Android > app. Luego, haz clic con el botón derecho en la carpeta res y selecciona New > Image Asset.

Se abrirá Asset Studio.

  1. En el menú Icon Type, haz clic en Action Bar and Tab Icons.
  2. Asigna un nombre al elemento ic_geolocate.
  3. Selecciona Clip Art como el tipo de elemento**.**
  4. Haz clic en el gráfico junto a Clip Art.

Esta acción abre la ventana Select Icon.

  1. Selecciona un ícono.

Puedes usar la barra de búsqueda para encontrar íconos relacionados con tu intent.

  1. Busca location y elige un ícono relacionado con la ubicación.

El ícono my location es el mismo que se utiliza en la app de Google Maps cuando un usuario quiere ajustar la cámara a su ubicación actual.

  1. Haz clic en OK > Next > Finish y confirma que haya una nueva carpeta llamada drawable que contenga tus nuevos archivos de íconos.

b9e0196137ed18ae.png

Agrega recursos de strings

  1. En el explorador del proyecto, haz clic en Android > app > res > values, y abre el archivo strings.xml.
  2. Agrega las siguientes líneas después de <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>

La primera línea se usa en la barra de la aplicación cuando hay espacio para incluir una etiqueta de texto junto al ícono. Las demás se usan para los marcadores que agregues al mapa.

Ahora, el código del archivo tiene el siguiente aspecto:

<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>

Agrega la barra de la aplicación

  1. En el explorador del proyecto, haz clic en Android > app. Luego, haz clic con el botón derecho en la carpeta res y selecciona New > Directory para crear un nuevo subdirectorio en app/src/main/res.
  2. Asígnale un nombre al directorio menu.
  3. Haz clic con el botón derecho en la carpeta menu y selecciona New > File.
  4. Asígnale un nombre al archivo menu.xml.
  5. Pega el siguiente código:
<?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>

Actualiza el estilo de la barra de la aplicación

  1. En el explorador del proyecto, expande Android > app > res > values, y abre el archivo styles.xml.
  2. En la etiqueta <style>, edita la propiedad superior para que sea "Theme.AppCompat.NoActionBar".
  3. Observa la propiedad name, que usarás en el paso siguiente.

styles.xml

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

Actualiza el tema de la app en AndroidManifest.xml.

  1. Haz clic en Android > app > manifests, y abre el archivo AndroidManifest.xml.
  2. Busca la línea android:theme y confirma que el valor sea @style/AppTheme o, de lo contrario, edítalo para sea ese.

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">

Ya puedes empezar a programar.

9. Inicializa la app

  1. En el explorador del proyecto, busca el archivo MapsActivity.java.

Se encuentra en la carpeta correspondiente al paquete que creaste para tu app en el paso 1.

8b0fa27d417f5f55.png

  1. Abre el archivo y estarás dentro del editor de código Java.

Importa el SDK de Places y otras dependencias

Agrega estas líneas en la parte superior de MapsActivity.java, en reemplazo de las sentencias de importación existentes.

Incluyen las importaciones existentes y agregan muchas más del código que se usa en este 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;

Actualiza la firma de la clase

La API de Places usa componentes de AndroidX para brindar retrocompatibilidad, de modo que debes definir la clase para que extienda AppCompatActivity. Reemplaza la extensión FragmentActivity que se define de forma predeterminada para una actividad en Maps.

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

Agrega variables de clases

A continuación, declara las distintas variables de clase que se usan en diferentes métodos de clase. Incluyen los elementos de la IU y códigos de estado. Deben estar justo debajo de la declaración de variables de 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;

Actualiza el método onCreate

Debes actualizar el método onCreate a fin de controlar los permisos del usuario durante el tiempo de ejecución para los servicios de ubicación, configurar los elementos de la IU y crear el cliente de la API de Places.

Agrega las siguientes líneas de código respecto de la barra de herramientas de acciones, la configuración de vistas y el cliente de Places al final del método onCreate() existente.

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);
    }

Agrega código para el menú de la barra de la aplicación

Estos dos métodos agregan el menú de la barra de la aplicación (con un solo elemento, el ícono de Elegir un lugar) y controlan el clic que haga el usuario en ese ícono.

Copia estos dos métodos en tu archivo después del método onCreate.

MapsActivity.java onCreateOptionsMenu() y 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);

        }
    }

Pruébalo

  1. En Android Studio, haz clic en Run o Run menu > Run ‘app'.

28bea91c68c36fb2.png

  1. Se te pedirá que selecciones el objetivo de la implementación. El emulador en ejecución debería aparecer en esta lista. Selecciónalo, y Android Studio implementa la app en el emulador por ti.

f44658ca91f6f41a.png

Después de unos segundos, se abre la app. Verás el mapa centrado en Sídney, Australia, con un solo botón y la lista de lugares sin completar.

68eb8c70f4748350.png

El enfoque del mapa no se mueve a la ubicación del usuario a menos que solicites permiso para acceder a la ubicación del dispositivo.

10. Solicita y controla los permisos de ubicación

Solicita permisos de ubicación una vez que esté listo el mapa

  1. Define un método llamado getLocationPermission que solicite los permisos del usuario.

Pega este código debajo del método onOptionsSelected que acabas de crear.

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. Agrega dos líneas al final del método onMapReady existente para habilitar los controles de zoom y solicitar los permisos de ubicación del usuario.

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();

    }

Controla el resultado de los permisos solicitados

Cuando el usuario responde el diálogo que le solicita el permiso, Android llama a esta devolución de llamada.

Pega este código después del método getLocationPermission():

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;
                }
            }
        }
    }

11. Obtén la ubicación actual y recupera los lugares probables

Cuando el usuario hace clic en Elegir un lugar en la barra de la aplicación, la app llama al método pickCurrentPlace(), el cual llama al método getDeviceLocation() que definiste antes. El método getDeviceLocation llama a otro método, getCurrentPlaceLikelihoods, después de recuperar la ubicación más reciente del dispositivo.

Llama a la API de findCurrentPlace y controla la respuesta

getCurrentPlaceLikelihoods construye una solicitud findCurrentPlaceRequest y llama a la tarea findCurrentPlace de la API de Places. Si la tarea se realiza correctamente, devuelve una respuesta findCurrentPlaceResponse, que contiene una lista de objetos placeLikelihood. Cada uno de ellos tiene una serie de propiedades, incluidos el nombre y la dirección del lugar, y el grado de probabilidad de que estés en ese lugar (un valor doble de 0 a 1). Este método controla la respuesta mediante la construcción de listas con los detalles de los lugares a partir de placeLikelihoods.

Este código itera a través de los cinco lugares más probables y agrega los que tienen un valor de probabilidad mayor que 0 a una lista que luego renderiza. Si quieres mostrar una cantidad mayor o menor a cinco, edita la constante M_MAX_ENTRIES.

Pega este código después del método onMapReady.

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());
                            }
                        }
                    }
                });
    }

Mueve la cámara del mapa a la ubicación actual del dispositivo

Si el usuario otorga el permiso, la app recuperará la ubicación más reciente del usuario y moverá la cámara para centrar el enfoque alrededor de esa ubicación.

Si el usuario rechaza el permiso, la app simplemente mueve la cámara a la ubicación predeterminada definida en las constantes que están al principio de esta página (en el código de muestra, es Sídney, Australia).

Pega este código después del método getPlaceLikelihoods():

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());
        }
    }

Comprueba los permisos de ubicación cuando el usuario hace clic en Elegir un lugar

Cuando el usuario presiona Elegir un lugar, este método busca los permisos de ubicación y, si el usuario todavía no concedió su permiso, se lo solicita.

Si ya lo hizo, entonces, el método llama a getDeviceLocation a fin de iniciar el proceso para obtener los lugares actuales probables.

  1. Agrega este método después de 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. Ahora que pickCurrentPlace está definido, busca la línea en onOptionsItemSelected() que llama a pickCurrentPlace, y quita el comentario.

MapsActivity.java onOptionItemSelected()

           case R.id.action_geolocate:

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

Pruébalo

Si ejecutas la app ahora y presionas Elegir un lugar, debería solicitarte los permisos de ubicación.

  • Si concedes el permiso, se guardará esa preferencia y ya no recibirás ese mensaje. Si rechazas el permiso, aparecerá la próxima vez que presiones el botón.
  • Aunque getPlaceLikelihoods recuperó los lugares actuales probables, aún no se muestran en ListView. En Android Studio, puedes hacer clic en ⌘6 para revisar los registros de Logcat sobre las sentencias etiquetadas como MapsActivity a fin de verificar que tus nuevos métodos funcionen correctamente.
  • Si otorgaste el permiso, los registros incluyen una sentencia para Latitude: y una para Longitude:, las cuales representan la ubicación detectada del dispositivo. Si usaste Google Maps y el menú ampliado del emulador antes para especificar una ubicación para el emulador, estas sentencias mostrarán esa ubicación.
  • Si la llamada a findCurrentPlace se realizó correctamente, los registros incluirán cinco sentencias que imprimen los nombres y las ubicaciones de los cinco lugares más probables.

d9896a245b81bf3.png

12. Propaga los datos del selector de Current Place

Configura un controlador para los lugares seleccionados

Pensemos en lo que queremos que suceda cuando el usuario haga clic en un elemento de la ListView. Para confirmar el lugar que el usuario eligió como aquel en el que se encuentra actualmente, puedes agregar un marcador al mapa en dicha ubicación. Si el usuario hace clic en el marcador, aparecerá una ventana emergente de información con el nombre y la dirección del lugar.

Pega este controlador de clics después del método pickCurrentPlace.

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));
        }
    };

Propaga los datos de la ListView

Ahora que tienes una lista de los lugares más probables para la ubicación actual del usuario, puedes mostrarle esas opciones en la ListView. También puedes configurar el objeto de escucha de clics de ListView para usar el controlador de clics que acabas de definir.

Pega este método después del controlador de clics:

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);
    }

Ahora que fillPlacesList está definido, busca la línea hacia el final de findPlaceLikelihoods que llama a fillPlacesList y elimina el comentario.

MapsActivity.java fillPlaceLikelihoods()

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

Este es todo el código necesario para el selector de Current Place.

13. Ejecuta la app

Prueba seleccionar un lugar

  1. Vuelve a ejecutar la app.

Esta vez, cuando presionas Elegir un lugar, la app propaga los lugares detectados cerca de la ubicación para completar la lista. En los alrededores de esta ubicación en Maui, hay lugares como Ululani's Hawaiian Shave Ice y Sugar Beach Bake Shop. Debido a que hay varios lugares muy cerca de las coordenadas de la ubicación, esta es una lista de los posibles lugares en los que podrías encontrarte.

  1. Haz clic en el nombre de un lugar en ListView.

Deberías ver que se agregó un marcador al mapa.

  1. Presiona el marcador.

Puedes ver los detalles del lugar.

e52303cc0de6a513.png 864c74342fb52a01.png

Prueba otra ubicación

Si quieres cambiar la ubicación y estás usando el emulador, la ubicación del dispositivo no se actualizará automáticamente cuando actualices las coordenadas de la ubicación en el menú extendido del emulador.

A fin de solucionar esto, sigue estos pasos para usar la app nativa de Google Maps y forzar las actualizaciones a la ubicación del emulador:

  1. Abre Google Maps.
  2. Presiona  > Location para cambiar la latitud y la longitud por coordenadas nuevas y, luego, presiona Send.
  3. Por ejemplo, puedes usar Latitude: 49.2768 y Longitude: -123.1142 para establecer la ubicación en el centro de Vancouver, Canadá.
  4. Verifica que Google Maps haya centrado el mapa según tus nuevas coordenadas. Es posible que debas presionar el botón Mi ubicación en la app de Google Maps para solicitar que vuelva a centrar el mapa.
  5. Regresa a tu app Current Place y presiona Elegir un lugar para obtener el mapa con las nuevas coordenadas y ver una lista nueva de los lugares posibles.

9adb99d1ce25c184.png

Eso es todo. Compilaste una aplicación simple que verifica los lugares correspondientes a tu ubicación actual y te muestra aquellos en los que es probable que te encuentres. ¡Disfrútala!

Ahora puedes ejecutar la app con las modificaciones que hiciste para completar este paso adicional.

14. Próximos pasos

Para evitar el robo de tu clave de API, debes protegerla de manera que solo tu app para Android pueda usarla. Si no le pones restricciones, cualquier persona que tenga tu clave podrá usarla para llamar a las API de Google Maps Platform y se facturará a tu cuenta.

Obtén tu certificado SHA-1

Lo necesitarás más adelante cuando restrinjas tus claves de API. El siguiente es un conjunto de instrucciones para obtener tu certificado de depuración.

En el caso de macOS o Linux, abre una ventana de la terminal y escribe lo siguiente:

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

Para Windows Vista y Windows 7, ejecuta el siguiente comando:

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

Deberías ver un resultado similar a este:

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

En la línea que comienza con SHA1, se incluye la huella digital SHA-1 del certificado. La huella digital es una secuencia de 20 números hexadecimales de dos dígitos separados por dos puntos.

Cuando estés listo para lanzar una app, sigue las instrucciones de esta documentación a fin de recuperar tu certificado de lanzamiento.

Agrega restricciones a tu clave de API

  1. En Cloud Console, navega hasta API y servicios > Credenciales.

La clave que usaste para esta app debería aparecer en Claves de API.

  1. Haz clic en 6454a04865d551e6.png para editar la configuración de la clave.

316b052c621ee91c.png

  1. En la página de claves de API, después de Restricciones de clave, configura las Restricciones de aplicaciones de la siguiente manera:
  2. Selecciona Apps de Android y sigue las instrucciones.
  3. Haz clic en Agregar un elemento.
  4. Ingresa el nombre de tu paquete y la huella digital del certificado SHA-1 (recuperada en la sección anterior).

Por ejemplo:

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. Para obtener más protección, establece las Restricciones de API de la siguiente manera:
  2. Después Restricciones de API, selecciona Restringir clave.
  3. Selecciona el SDK de Maps para Android y la API de Places.
  4. Haz clic en Listo y, luego, en Guardar.

15. Felicitaciones

Compilaste una app simple que verifica los lugares más probables en la ubicación actual y agrega un marcador al mapa para el sitio que el usuario seleccione.

Más información