Selecciona el lugar actual y muestra información en un mapa

En este instructivo, se explica cómo encontrar la ubicación actual de un dispositivo Android y cómo mostrar información detallada del lugar (una empresa o cualquier otro lugar de interés) en esa ubicación. Sigue este instructivo a fin de compilar una app para Android con Maps SDK for Android, Places SDK for Android y el proveedor de ubicación combinada en las API de ubicación de Servicios de Google Play.

Obtén el código

En GitHub, clona o descarga el repositorio de muestras de la versión 2 de la API de Google Maps para Android.

Configura tu proyecto de desarrollo

Sigue estos pasos para crear el proyecto del instructivo en Android Studio:

  1. Descarga Android Studio y, luego, instálalo.
  2. Agrega el paquete de Servicios de Google Play a Android Studio.
  3. Clona o descarga el repositorio de muestras de la versión 2 de la API de Google Maps para Android si no lo hiciste cuando empezaste a leer este instructivo.
  4. Importa el proyecto del instructivo:

    • En Android Studio, selecciona Archivo > Nuevo > Importar proyecto.
    • Ve a la ubicación en que guardaste el repositorio de muestras de la versión 2 de la API de Google Maps para Android tras descargarlo.
    • Busca el proyecto CurrentPlaceDetailsOnMap en esta ubicación:
      PATH-TO-SAVED-REPO/android-samples/tutorials/CurrentPlaceDetailsOnMap
    • Selecciona el directorio del proyecto y haz clic en Aceptar. Android Studio ahora compilará tu proyecto con la herramienta de compilación Gradle.

Obtén una clave de API y habilita las API necesarias

Si deseas completar este instructivo, necesitas una clave de API de Google autorizada a fin de utilizar Maps SDK for Android y Places SDK for Android.

Haz clic en el siguiente botón para obtener una clave y activar las API.

Comenzar

Para obtener más detalles, consulta la guía completa para obtener una clave de API.

Agrega la clave de API a tu app

  1. Edita el archivo gradle.properties de tu proyecto.
  2. Pega la clave de API en el valor de la propiedad GOOGLE_MAPS_API_KEY:

    GOOGLE_MAPS_API_KEY=PASTE-YOUR-API-KEY-HERE

    Cuando compilas tu app, Gradle copia la clave de API en el manifiesto de Android de la app. El archivo build.gradle de la app contiene la siguiente línea, que mapea la string google_maps_key del manifiesto con la propiedad GOOGLE_MAPS_API_KEY de Gradle:

    resValue "string", "google_maps_key",
            (project.findProperty("GOOGLE_MAPS_API_KEY") ?: "")
    

Compila y ejecuta tu app

  1. Conecta un dispositivo Android a tu computadora. Sigue las instrucciones a fin de habilitar las opciones para desarrolladores en tu dispositivo Android y configurar el sistema para que lo detecte. También puedes utilizar el Administrador de dispositivos virtuales de Android (AVD) para configurar esos dispositivos. Al elegir un emulador, asegúrate de seleccionar una imagen que incluya las API de Google. Para obtener más detalles, consulta la guía de introducción.
  2. En Android Studio, haz clic en la opción Ejecutar del menú (o en el ícono del botón de reproducción). Selecciona un dispositivo según se solicite.

Android Studio invoca a Gradle para compilar la app y, luego, la ejecuta en el dispositivo o el emulador. Deberías ver un mapa con una serie de marcadores centrados cerca de tu ubicación actual, similar al de la imagen incluida en esta página.

Solución de problemas:

  • Si no ves un mapa, verifica si obtuviste una clave de API y la agregaste a la app, como se describió anteriormente. Consulta el registro en Android Monitor de Android Studio para ver si hay mensajes de error acerca de la clave de API.
  • Si el mapa muestra solo un marcador ubicado en el Puente del puerto de Sídney (la ubicación predeterminada que se especificó en la app), verifica que le otorgaste permiso de ubicación a la app. Esta solicita ese permiso en el tiempo de ejecución, según el patrón descrito en la guía sobre permisos de Android. Ten en cuenta que también puedes configurar los permisos directamente en el dispositivo. Para ello, selecciona Configuración > Apps > nombre de la app > Permisos > Ubicación. Para obtener detalles sobre cómo controlar los permisos en tu código, consulta la guía que se incluye a continuación sobre cómo solicitar permisos de ubicación en tu app.
  • Utiliza las herramientas de depuración de Android Studio para ver los registros y depurar la app.

Comprende el código

En esta sección del instructivo, se explican los componentes más importantes de la app CurrentPlaceDetailsOnMap para ayudarte a comprender cómo compilar una app similar.

Crea una instancia de los clientes de la API de Places

Las siguientes interfaces brindan los principales puntos de entrada a Places SDK for Android:

  • El cliente GeoDataClient proporciona acceso a la base de datos de Google con información sobre las empresas y los lugares locales.
  • El cliente PlaceDetectionClient proporciona acceso rápido al lugar donde se encuentra actualmente el dispositivo y brinda la posibilidad de informar la ubicación del dispositivo en un lugar específico.

La interfaz LocationServices es el principal punto de entrada para los Servicios de ubicación de Android.

Para utilizar las API, crea una instancia de los clientes GeoDataClient, PlaceDetectionClient y FusedLocationProviderClient en el método onCreate() de tu fragmento o actividad, como se ejemplifica en la siguiente muestra de código:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Construct a GeoDataClient.
    mGeoDataClient = Places.getGeoDataClient(this, null);

    // Construct a PlaceDetectionClient.
    mPlaceDetectionClient = Places.getPlaceDetectionClient(this, null);

    // Construct a FusedLocationProviderClient.
    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
}

Solicita permiso de ubicación

Tu app debe solicitar permiso de ubicación para determinar la ubicación del dispositivo y permitir que el usuario presione el botón Mi ubicación en el mapa.

En este instructivo, se proporciona el código necesario para solicitar el permiso de ubicación precisa. Para obtener más detalles, consulta la guía sobre permisos de Android.

  1. Agrega el permiso como componente secundario del elemento <manifest> en tu manifiesto de Android:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.currentplacedetailsonmap">
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    </manifest>
    
  2. En tu app, solicita permisos de tiempo de ejecución para que el usuario tenga la posibilidad de autorizar o rechazar el permiso de ubicación. El siguiente código verifica si el usuario otorgó el permiso de ubicación precisa y, si no lo hizo, se lo solicita:

    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.
         */
        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);
        }
    }
    
  3. Anula la devolución de llamada onRequestPermissionsResult() para controlar el resultado de la solicitud de permiso:

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

    Más adelante en este instructivo, se describe el método updateLocationUI().

Agrega un mapa

Muestra un mapa con Maps SDK for Android.

  1. Agrega un elemento <fragment> al archivo de diseño de tu actividad, activity_maps.xml. Este elemento define un fragmento SupportMapFragment para que actúe como contenedor del mapa y proporcione acceso al objeto GoogleMap. En el instructivo, se utiliza la versión de la biblioteca de compatibilidad de Android correspondiente al fragmento de mapa para garantizar la retrocompatibilidad con las versiones anteriores del marco de trabajo de Android.

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.example.currentplacedetailsonmap.MapsActivityCurrentPlace" />
    
    
  2. En el método onCreate() de tu actividad, establece el archivo de diseño como la vista de contenido:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
    }
    
  3. Implementa la interfaz OnMapReadyCallback y anula el método onMapReady() para configurar el mapa cuando el objeto GoogleMap esté disponible:

    public void onMapReady(GoogleMap map) {
        mMap = map;
    
        // Do other setup activities here too, as described elsewhere in this tutorial.
    
        // Turn on the My Location layer and the related control on the map.
        updateLocationUI();
    
        // Get the current location of the device and set the position of the map.
        getDeviceLocation();
    }
    
  4. En el método onCreate() de tu actividad, llama a FragmentManager.findFragmentById() a fin de controlar el fragmento de mapa. Luego, utiliza getMapAsync() a fin de registrarte para la devolución de llamada del mapa:

    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
    
  5. Escribe un método updateLocationUI() para establecer los controles de ubicación en el mapa. Si el usuario otorgó permisos de ubicación, habilita la capa Mi ubicación y el control relacionado en el mapa. De lo contrario, inhabilítalos y establece la ubicación actual en null:

    private void updateLocationUI() {
        if (mMap == null) {
            return;
        }
        try {
            if (mLocationPermissionGranted) {
                mMap.setMyLocationEnabled(true);
                mMap.getUiSettings().setMyLocationButtonEnabled(true);
            } else {
                mMap.setMyLocationEnabled(false);
                mMap.getUiSettings().setMyLocationButtonEnabled(false);
                mLastKnownLocation = null;
                getLocationPermission();
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }
    

Obtén la ubicación del dispositivo Android y posiciona el mapa

Utiliza el proveedor de ubicación combinada para buscar la última ubicación conocida del dispositivo y, luego, posiciona el mapa en función de esa ubicación. En este instructivo, se proporciona el código necesario. Para obtener más detalles sobre cómo obtener la ubicación del dispositivo, consulta la guía del proveedor de ubicación combinada en las API de ubicación de Servicios de Google Play.

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 locationResult = mFusedLocationProviderClient.getLastLocation();
            locationResult.addOnCompleteListener(this, new OnCompleteListener() {
                @Override
                public void onComplete(@NonNull Task task) {
                    if (task.isSuccessful()) {
                        // Set the map's camera position to the current location of the device.
                        mLastKnownLocation = task.getResult();
                        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));
                        mMap.getUiSettings().setMyLocationButtonEnabled(false);
                    }
                }
            });
        }
    } catch(SecurityException e)  {
        Log.e("Exception: %s", e.getMessage());
    }
}

Obtén el lugar actual

Utiliza Places SDK for Android a fin de obtener una lista de los posibles lugares en la ubicación actual del dispositivo. En este contexto, un lugar es una empresa o cualquier otro lugar de interés.

En este instructivo, se muestra cómo obtener el lugar actual cuando el usuario hace clic en el botón Obtener lugar. Ofrece al usuario una lista de los lugares que puede elegir y, luego, agrega un marcador al mapa en la ubicación del lugar seleccionado. En el instructivo, se proporciona el código necesario para interactuar con Places SDK for Android. Si deseas obtener más detalles, consulta la guía sobre cómo obtener el lugar actual.

  1. Crea un archivo de diseño (current_place_menu.xml) para el menú de opciones y anula el método onCreateOptionsMenu() a fin de configurar dicho menú. Consulta la app de muestra que se incluye para obtener el código.
  2. Anula el método onOptionsItemSelected() para obtener el lugar actual cuando el usuario hace clic en la opción Obtener lugar:
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.option_get_place) {
            showCurrentPlace();
        }
        return true;
    }
    
  3. Crea un método showCurrentPlace() para obtener una lista de los posibles lugares en la ubicación actual del dispositivo:

    private void showCurrentPlace() {
        if (mMap == null) {
            return;
        }
    
        if (mLocationPermissionGranted) {
            // 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);
    
            // Use the builder to create a FindCurrentPlaceRequest.
            FindCurrentPlaceRequest request =
                    FindCurrentPlaceRequest.newInstance(placeFields);
    
            // 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
            Task<FindCurrentPlaceResponse> placeResult =
                    mPlacesClient.findCurrentPlace(request);
            placeResult.addOnCompleteListener (new OnCompleteListener<FindCurrentPlaceResponse>() {
                @Override
                public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                    if (task.isSuccessful() && task.getResult() != null) {
                        FindCurrentPlaceResponse likelyPlaces = task.getResult();
    
                        // Set the count, handling cases where less than 5 entries are returned.
                        int count;
                        if (likelyPlaces.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                            count = likelyPlaces.getPlaceLikelihoods().size();
                        } else {
                            count = M_MAX_ENTRIES;
                        }
    
                        int i = 0;
                        mLikelyPlaceNames = new String[count];
                        mLikelyPlaceAddresses = new String[count];
                        mLikelyPlaceAttributions = new List[count];
                        mLikelyPlaceLatLngs = new LatLng[count];
    
                        for (PlaceLikelihood placeLikelihood : likelyPlaces.getPlaceLikelihoods()) {
                            // Build a list of likely places to show the user.
                            mLikelyPlaceNames[i] = placeLikelihood.getPlace().getName();
                            mLikelyPlaceAddresses[i] = placeLikelihood.getPlace().getAddress();
                            mLikelyPlaceAttributions[i] = placeLikelihood.getPlace()
                                    .getAttributions();
                            mLikelyPlaceLatLngs[i] = placeLikelihood.getPlace().getLatLng();
    
                            i++;
                            if (i > (count - 1)) {
                                break;
                            }
                        }
    
                        // Show a dialog offering the user the list of likely places, and add a
                        // marker at the selected place.
                        MapsActivityCurrentPlace.this.openPlacesDialog();
                    }
                    else {
                        Log.e(TAG, "Exception: %s", task.getException());
                    }
                }
            });
        } 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();
        }
    }
    
  4. Crea un método openPlacesDialog() para mostrar un formulario que permita al usuario seleccionar un lugar de una lista de posibles lugares. Agrega un marcador del lugar seleccionado en el mapa. El contenido del marcador incluirá el nombre y la dirección del lugar, así como las atribuciones que proporciona la API:

    private void openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // The "which" argument contains the position of the selected item.
                LatLng markerLatLng = mLikelyPlaceLatLngs[which];
                String markerSnippet = mLikelyPlaceAddresses[which];
                if (mLikelyPlaceAttributions[which] != null) {
                    markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[which];
                }
    
                // Add a marker for the selected place, with an info window
                // showing information about that place.
                mMap.addMarker(new MarkerOptions()
                        .title(mLikelyPlaceNames[which])
                        .position(markerLatLng)
                        .snippet(markerSnippet));
    
                // Position the map's camera at the location of the marker.
                mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerLatLng,
                        DEFAULT_ZOOM));
            }
        };
    
        // Display the dialog.
        AlertDialog dialog = new AlertDialog.Builder(this)
                .setTitle(R.string.pick_place)
                .setItems(mLikelyPlaceNames, listener)
                .show();
    }
    
  5. Crea un diseño personalizado para el contenido de la ventana de información. De esta forma, podrás mostrar varias líneas de contenido en esa ventana. Primero, agrega un archivo de diseño XML, custom_info_contents.xml, que contenga una vista de texto para el título de la ventana de información y otra para el fragmento (es decir, el contenido de texto de la ventana):

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layoutDirection="locale"
        android:orientation="vertical">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textColor="#ff000000"
            android:textStyle="bold" />
    
        <TextView
            android:id="@+id/snippet"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ff7f7f7f" />
    </LinearLayout>
    
    
  6. Implementa la interfaz InfoWindowAdapter para aumentar el diseño y cargar el contenido de la ventana de información:

    @Override
    public void onMapReady(GoogleMap map) {
        // Do other setup activities here too, as described elsewhere in this tutorial.
        mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
    
        @Override
        // Return null here, so that getInfoContents() is called next.
        public View getInfoWindow(Marker arg0) {
            return null;
        }
    
        @Override
        public View getInfoContents(Marker marker) {
            // Inflate the layouts for the info window, title and snippet.
            View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_contents, null);
    
            TextView title = ((TextView) infoWindow.findViewById(R.id.title));
            title.setText(marker.getTitle());
    
            TextView snippet = ((TextView) infoWindow.findViewById(R.id.snippet));
            snippet.setText(marker.getSnippet());
    
            return infoWindow;
          }
        });
    }
    

Guarda el estado del mapa

Guarda la posición de la cámara del mapa y la ubicación del dispositivo. Cuando un usuario rota un dispositivo Android o realiza cambios en la configuración, el marco de trabajo de este sistema operativo destruye y reconstruye la actividad en Maps. Para garantizar una experiencia del usuario sin inconvenientes, se recomienda almacenar el estado de la aplicación relevante y restablecerlo cuando sea necesario.

En este instructivo, se proporciona todo el código necesario para guardar el estado de un mapa. Para obtener más detalles, consulta la guía sobre el paquete savedInstanceState.

  1. En tu actividad en Maps, define valores de clave para almacenar el estado de la actividad:

    private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION = "location";
    
  2. Implementa la devolución de llamada onSaveInstanceState() para guardar el estado cuando se detenga la actividad:

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (mMap != null) {
            outState.putParcelable(KEY_CAMERA_POSITION, mMap.getCameraPosition());
            outState.putParcelable(KEY_LOCATION, mLastKnownLocation);
            super.onSaveInstanceState(outState);
        }
    }
    
  3. En el método onCreate() de tu actividad, recupera la ubicación del dispositivo y la posición de la cámara del mapa (si ya las guardaste):

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) {
            mCurrentLocation = savedInstanceState.getParcelable(KEY_LOCATION);
            mCameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION);
        }
        ...
    }