Lorsque vous remplissez une adresse de livraison, des informations de facturation ou des informations sur un événement, l'activation de la fonctionnalité Place Autocomplete dans les formulaires permet aux utilisateurs de réduire le nombre de frappes et les erreurs lorsqu'ils saisissent des informations d'adresse. Ce tutoriel explique comment activer un champ de saisie avec Place Autocomplete, renseigner les champs du formulaire d'adresse avec les éléments d'adresse de l'adresse sélectionnée par l'utilisateur et présenter l'adresse sélectionnée sur une carte pour faciliter la confirmation visuelle.
Vidéos : Améliorer les formulaires d'adresse avec Place Autocomplete
Formulaires d'adresse
Android
iOS
Web
Google Maps Platform fournit un widget Place Autocomplete pour les plates-formes mobiles et le Web. Le widget, illustré dans les figures précédentes, fournit une boîte de dialogue de recherche avec une fonctionnalité de saisie semi-automatique intégrée, que vous pouvez même optimiser pour les recherches basées sur la position.
Obtenir le code
Clonez ou téléchargez le dépôt des démonstrations du SDK Google Places pour Android à partir de GitHub.
Consultez la version Java de l'activité :
/* * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.placesdemo; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.ViewStub; import android.widget.Button; import android.widget.CheckBox; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import com.example.placesdemo.databinding.AutocompleteAddressActivityBinding; 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.GoogleMapOptions; 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.MapStyleOptions; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.libraries.places.api.Places; import com.google.android.libraries.places.api.model.AddressComponent; import com.google.android.libraries.places.api.model.AddressComponents; import com.google.android.libraries.places.api.model.Place; import com.google.android.libraries.places.api.model.TypeFilter; import com.google.android.libraries.places.api.net.PlacesClient; import com.google.android.libraries.places.widget.Autocomplete; import com.google.android.libraries.places.widget.model.AutocompleteActivityMode; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static com.google.maps.android.SphericalUtil.computeDistanceBetween; /** * Activity for using Place Autocomplete to assist filling out an address form. */ @SuppressWarnings("FieldCanBeLocal") public class AutocompleteAddressActivity extends AppCompatActivity implements OnMapReadyCallback { private static final String TAG = "ADDRESS_AUTOCOMPLETE"; private static final String MAP_FRAGMENT_TAG = "MAP"; private LatLng coordinates; private boolean checkProximity = false; private SupportMapFragment mapFragment; private GoogleMap map; private Marker marker; private PlacesClient placesClient; private View mapPanel; private LatLng deviceLocation; private static final double acceptedProximity = 150; private AutocompleteAddressActivityBinding binding; View.OnClickListener startAutocompleteIntentListener = view -> { view.setOnClickListener(null); startAutocompleteIntent(); }; private final ActivityResultLauncher<Intent> startAutocomplete = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent != null) { Place place = Autocomplete.getPlaceFromIntent(intent); // Write a method to read the address components from the Place // and populate the form with the address components Log.d(TAG, "Place: " + place.getAddressComponents()); fillInAddress(place); } } else if (result.getResultCode() == Activity.RESULT_CANCELED) { // The user canceled the operation. Log.i(TAG, "User canceled autocomplete"); } }); @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) { super.onActivityResult(requestCode, resultCode, intent); binding.autocompleteAddress1.setOnClickListener(startAutocompleteIntentListener); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = AutocompleteAddressActivityBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); // Retrieve a PlacesClient (previously initialized - see MainActivity) placesClient = Places.createClient(this); // Attach an Autocomplete intent to the Address 1 EditText field binding.autocompleteAddress1.setOnClickListener(startAutocompleteIntentListener); // Update checkProximity when user checks the checkbox CheckBox checkProximityBox = findViewById(R.id.checkbox_proximity); checkProximityBox.setOnCheckedChangeListener((view, isChecked) -> { // Set the boolean to match user preference for when the Submit button is clicked checkProximity = isChecked; }); // Submit and optionally check proximity Button saveButton = findViewById(R.id.autocomplete_save_button); saveButton.setOnClickListener(v -> saveForm()); // Reset the form Button resetButton = findViewById(R.id.autocomplete_reset_button); resetButton.setOnClickListener(v -> clearForm()); } private void startAutocompleteIntent() { // Set the fields to specify which types of place data to // return after the user has made a selection. List<Place.Field> fields = Arrays.asList(Place.Field.ADDRESS_COMPONENTS, Place.Field.LAT_LNG, Place.Field.VIEWPORT); // Build the autocomplete intent with field, country, and type filters applied Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields) .setCountries(Arrays.asList("US")) .setTypesFilter(new ArrayList<String>() {{ add(TypeFilter.ADDRESS.toString().toLowerCase()); }}) .build(this); startAutocomplete.launch(intent); } @Override public void onMapReady(@NonNull GoogleMap googleMap) { map = googleMap; try { // Customise the styling of the base map using a JSON object defined // in a string resource. boolean success = map.setMapStyle( MapStyleOptions.loadRawResourceStyle(this, R.raw.style_json)); if (!success) { Log.e(TAG, "Style parsing failed."); } } catch (Resources.NotFoundException e) { Log.e(TAG, "Can't find style. Error: ", e); } map.moveCamera(CameraUpdateFactory.newLatLngZoom(coordinates, 15f)); marker = map.addMarker(new MarkerOptions().position(coordinates)); } private void fillInAddress(Place place) { AddressComponents components = place.getAddressComponents(); StringBuilder address1 = new StringBuilder(); StringBuilder postcode = new StringBuilder(); // Get each component of the address from the place details, // and then fill-in the corresponding field on the form. // Possible AddressComponent types are documented at https://goo.gle/32SJPM1 if (components != null) { for (AddressComponent component : components.asList()) { String type = component.getTypes().get(0); switch (type) { case "street_number": { address1.insert(0, component.getName()); break; } case "route": { address1.append(" "); address1.append(component.getShortName()); break; } case "postal_code": { postcode.insert(0, component.getName()); break; } case "postal_code_suffix": { postcode.append("-").append(component.getName()); break; } case "locality": binding.autocompleteCity.setText(component.getName()); break; case "administrative_area_level_1": { binding.autocompleteState.setText(component.getShortName()); break; } case "country": binding.autocompleteCountry.setText(component.getName()); break; } } } binding.autocompleteAddress1.setText(address1.toString()); binding.autocompletePostal.setText(postcode.toString()); // After filling the form with address components from the Autocomplete // prediction, set cursor focus on the second address line to encourage // entry of sub-premise information such as apartment, unit, or floor number. binding.autocompleteAddress2.requestFocus(); // Add a map for visual confirmation of the address showMap(place); } private void showMap(Place place) { coordinates = place.getLatLng(); // It isn't possible to set a fragment's id programmatically so we set a tag instead and // search for it using that. mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG); // We only create a fragment if it doesn't already exist. if (mapFragment == null) { mapPanel = ((ViewStub) findViewById(R.id.stub_map)).inflate(); GoogleMapOptions mapOptions = new GoogleMapOptions(); mapOptions.mapToolbarEnabled(false); // To programmatically add the map, we first create a SupportMapFragment. mapFragment = SupportMapFragment.newInstance(mapOptions); // Then we add it using a FragmentTransaction. getSupportFragmentManager() .beginTransaction() .add(R.id.confirmation_map, mapFragment, MAP_FRAGMENT_TAG) .commit(); mapFragment.getMapAsync(this); } else { updateMap(coordinates); } } private void updateMap(LatLng latLng) { marker.setPosition(latLng); map.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f)); if (mapPanel.getVisibility() == View.GONE) { mapPanel.setVisibility(View.VISIBLE); } } private void saveForm() { Log.d(TAG, "checkProximity = " + checkProximity); if (checkProximity) { checkLocationPermissions(); } else { Toast.makeText( this, R.string.autocomplete_skipped_message, Toast.LENGTH_SHORT) .show(); } } private void clearForm() { binding.autocompleteAddress1.setText(""); binding.autocompleteAddress2.getText().clear(); binding.autocompleteCity.getText().clear(); binding.autocompleteState.getText().clear(); binding.autocompletePostal.getText().clear(); binding.autocompleteCountry.getText().clear(); if (mapPanel != null) { mapPanel.setVisibility(View.GONE); } binding.autocompleteAddress1.requestFocus(); } // Register the permissions callback, which handles the user's response to the // system permissions dialog. Save the return value, an instance of // ActivityResultLauncher, as an instance variable. private final ActivityResultLauncher<String> requestPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { // Since ACCESS_FINE_LOCATION is the only permission in this sample, // run the location comparison task once permission is granted. // Otherwise, check which permission is granted. getAndCompareLocations(); } else { // Fallback behavior if user denies permission Log.d(TAG, "User denied permission"); } }); private void checkLocationPermissions() { if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { getAndCompareLocations(); } else { requestPermissionLauncher.launch( ACCESS_FINE_LOCATION); } } @SuppressLint("MissingPermission") private void getAndCompareLocations() { // TODO: Detect and handle if user has entered or modified the address manually and update // the coordinates variable to the Lat/Lng of the manually entered address. May use // Geocoding API to convert the manually entered address to a Lat/Lng. LatLng enteredLocation = coordinates; map.setMyLocationEnabled(true); FusedLocationProviderClient fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); fusedLocationClient.getLastLocation() .addOnSuccessListener(this, location -> { // Got last known location. In some rare situations this can be null. if (location == null) { return; } deviceLocation = new LatLng(location.getLatitude(), location.getLongitude()); Log.d(TAG, "device location = " + deviceLocation); Log.d(TAG, "entered location = " + enteredLocation.toString()); // Use the computeDistanceBetween function in the Maps SDK for Android Utility Library // to use spherical geometry to compute the distance between two Lat/Lng points. double distanceInMeters = computeDistanceBetween(deviceLocation, enteredLocation); if (distanceInMeters <= acceptedProximity) { Log.d(TAG, "location matched"); // TODO: Display UI based on the locations matching } else { Log.d(TAG, "location not matched"); // TODO: Display UI based on the locations not matching } }); } }
Activer les API
Pour appliquer ces recommandations, vous devez activer les API suivantes dans la console Google Cloud :
- SDK Maps pour Android (ou l'API correspondant à la plate-forme de votre choix)
- API Places
Pour en savoir plus sur la configuration, consultez Configurer votre projet Google Cloud.
Ajouter la saisie semi-automatique aux champs de saisie
Cette section explique comment ajouter Place Autocomplete à un formulaire d'adresse.
Ajouter le widget Place Autocomplete
Dans Android, vous pouvez ajouter le widget Autocomplete à l'aide d'un intent Autocomplete qui lance Place Autocomplete à partir du champ de saisie de la ligne d'adresse 1. L'utilisateur commencera à saisir du texte à cet endroit, et pourra sélectionner son adresse dans la liste des prédictions de saisie semi-automatique.
Commencez par préparer un lanceur d'activité avec ActivityResultLauncher
, qui écoutera le résultat de l'activité lancée. Le rappel du résultat contiendra un objet Place correspondant à l'adresse sélectionnée par l'utilisateur dans les prédictions de saisie semi-automatique.
private final ActivityResultLauncher<Intent> startAutocomplete = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent != null) { Place place = Autocomplete.getPlaceFromIntent(intent); // Write a method to read the address components from the Place // and populate the form with the address components Log.d(TAG, "Place: " + place.getAddressComponents()); fillInAddress(place); } } else if (result.getResultCode() == Activity.RESULT_CANCELED) { // The user canceled the operation. Log.i(TAG, "User canceled autocomplete"); } });
Ensuite, créez l'intent Place Autocomplete, et définissez ses champs ainsi que ses propriétés de lieu et de type avec Autocomplete.IntentBuilder
.
Enfin, lancez l'intent à l'aide d'ActivityResultLauncher
, défini dans l'exemple de code précédent.
private void startAutocompleteIntent() { // Set the fields to specify which types of place data to // return after the user has made a selection. List<Place.Field> fields = Arrays.asList(Place.Field.ADDRESS_COMPONENTS, Place.Field.LAT_LNG, Place.Field.VIEWPORT); // Build the autocomplete intent with field, country, and type filters applied Intent intent = new Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields) .setCountries(Arrays.asList("US")) .setTypesFilter(new ArrayList<String>() {{ add(TypeFilter.ADDRESS.toString().toLowerCase()); }}) .build(this); startAutocomplete.launch(intent); }
Traiter l'adresse renvoyée par Place Autocomplete
En définissant ActivityResultLauncher
précédemment, vous avez également défini l'action à effectuer lorsque le résultat de l'activité est renvoyé dans le rappel. Si l'utilisateur a sélectionné une prédiction, elle sera transmise à l'intent contenu dans l'objet de résultat. Comme l'intent a été créé par Autocomplete.IntentBuilder
, la méthode Autocomplete.getPlaceFromIntent()
peut en extraire l'objet Place.
private final ActivityResultLauncher<Intent> startAutocomplete = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent != null) { Place place = Autocomplete.getPlaceFromIntent(intent); // Write a method to read the address components from the Place // and populate the form with the address components Log.d(TAG, "Place: " + place.getAddressComponents()); fillInAddress(place); } } else if (result.getResultCode() == Activity.RESULT_CANCELED) { // The user canceled the operation. Log.i(TAG, "User canceled autocomplete"); } });
Appelez alors Place.getAddressComponents()
et insérez les composants de l'adresse sélectionnée par l'utilisateur dans les champs de saisie correspondants du formulaire d'adresse.
Un exemple d'implémentation pour renseigner les champs du formulaire d'adresse est partagé dans la méthode fillInAddress
de l'exemple de code fourni dans la section Obtenir le code de cette page.
Par rapport à une saisie manuelle, capturer les données d'adresse à partir d'une prédiction vous permet de vous assurer que l'adresse est exacte et connue (y compris des services postaux). L'utilisateur a, quant à lui, moins de texte à saisir manuellement.
Éléments à prendre en compte pour implémenter Place Autocomplete
Place Autocomplete comporte de nombreuses options offrant une implémentation flexible de cette solution si vous ne souhaitez pas vous limiter au widget. Vous pouvez utiliser une combinaison de services afin d'obtenir exactement les données dont vous avez besoin pour bien mettre en correspondance un lieu.
Pour un formulaire ADDRESS, définissez le paramètre de type sur
address
afin de limiter les correspondances aux adresses postales complètes. En savoir plus sur les types pris en charge dans les requêtes Place AutocompleteDéfinissez les restrictions et limitations appropriées si votre recherche ne doit pas porter sur le monde entier. Plusieurs paramètres vous permettent de limiter ou restreindre les correspondances à des régions spécifiques.
Utilisez
RectangularBounds
pour définir les limites rectangulaires afin de restreindre la recherche à une zone etsetLocationRestriction()
pour que seules les adresses dans ces zones soient renvoyées.Utilisez
setCountries()
pour limiter les réponses à un ensemble de pays précis.
Faites en sorte que les champs restent modifiables au cas où la mise en correspondance soit manquée et autorisez les utilisateurs à modifier l'adresse si nécessaire. Étant donné que la plupart des adresses renvoyées par Place Autocomplete ne contiennent pas de numéros complémentaires (tels que les numéros d'appartement, de résidence ou de bâtiment), vous pouvez positionner le curseur dans la ligne d'adresse 2 pour inciter l'utilisateur à renseigner cette information si nécessaire.
Présenter l'adresse sous forme visuelle pour confirmation
Lorsque les utilisateurs doivent indiquer leur adresse, affichez-la sur une carte pour qu'ils la confirment. Ils seront d'autant plus confiants de ne pas avoir commis d'erreur.
La figure suivante montre une carte sous l'adresse avec un repère à l'endroit correspondant.
L'exemple suivant suit les étapes de base pour ajouter une carte sous Android. Reportez-vous à la documentation pour plus de détails.
- Ajouter le
SupportMapFragment
(dans ce cas, un fragment en mode dynamique) - Obtenir un handle vers le fragment et enregistrer le rappel
- Attribuer un style et ajouter un repère à la carte
- Désactiver les commandes de la carte
Ajouter le SupportMapFragment
Tout d'abord, ajoutez un fragment SupportMapFragment
au fichier XML de mise en page.
<fragment android:name="com.google.android.gms.maps.SupportMapFragment" android:id="@+id/confirmation_map" android:layout_width="match_parent" android:layout_height="match_parent"/>
Ajoutez ensuite le fragment par programmation s'il n'existe pas encore.
private void showMap(Place place) { coordinates = place.getLatLng(); // It isn't possible to set a fragment's id programmatically so we set a tag instead and // search for it using that. mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG); // We only create a fragment if it doesn't already exist. if (mapFragment == null) { mapPanel = ((ViewStub) findViewById(R.id.stub_map)).inflate(); GoogleMapOptions mapOptions = new GoogleMapOptions(); mapOptions.mapToolbarEnabled(false); // To programmatically add the map, we first create a SupportMapFragment. mapFragment = SupportMapFragment.newInstance(mapOptions); // Then we add it using a FragmentTransaction. getSupportFragmentManager() .beginTransaction() .add(R.id.confirmation_map, mapFragment, MAP_FRAGMENT_TAG) .commit(); mapFragment.getMapAsync(this); } else { updateMap(coordinates); } }
Obtenir un handle vers le fragment et enregistrer le rappel
Pour obtenir un handle vers le fragment, appelez la méthode
FragmentManager.findFragmentById
et transmettez-lui l'ID de ressource du fragment dans votre fichier de mise en page. Si vous avez ajouté le fragment en mode dynamique, ignorez cette étape, car vous avez déjà récupéré le handle.Appelez la méthode
getMapAsync
pour définir le rappel sur le fragment.
Par exemple, si vous avez ajouté le fragment en mode statique :
Kotlin
val mapFragment = supportFragmentManager .findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this)
Java
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this);
Attribuer un style et ajouter un repère à la carte
Lorsque la carte est prête, définissez le style, centrez la caméra et ajoutez un repère aux coordonnées de l'adresse saisie. Le code suivant utilise un style défini dans un objet JSON, mais vous pouvez également charger un ID de carte défini grâce aux styles de cartes basés dans le cloud.
@Override public void onMapReady(@NonNull GoogleMap googleMap) { map = googleMap; try { // Customise the styling of the base map using a JSON object defined // in a string resource. boolean success = map.setMapStyle( MapStyleOptions.loadRawResourceStyle(this, R.raw.style_json)); if (!success) { Log.e(TAG, "Style parsing failed."); } } catch (Resources.NotFoundException e) { Log.e(TAG, "Can't find style. Error: ", e); } map.moveCamera(CameraUpdateFactory.newLatLngZoom(coordinates, 15f)); marker = map.addMarker(new MarkerOptions().position(coordinates)); }
Voir un exemple de code complet
Désactiver les commandes de la carte
Pour simplifier la carte en affichant seulement la position, envisagez de désactiver les commandes que vous estimez superflues (boussole, barre d'outils ou d'autres éléments géographiques intégrés, par exemple). Sous Android, vous pouvez aussi activer le mode simplifié afin de limiter les interactions disponibles.