Place Autocomplete + Search UI + Maps SDK
NOTE
This example is a part of the Search SDK for Android sample app. You can find the values for all referenced resources in the res
directory. For example, see res/values/strings.xml
for R.string.*
references used in this example.
Besides Place Autocomplete and Search UI this example also uses utility class SampleAppUtils.kt and Mapbox Maps SDK. You need to add it as a dependency using the Maps SDK v10 installation instructions.
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayoutxmlns: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:background="@color/windowBackground"tools:context=".AddressAutofillUiActivity"> <EditTextandroid:id="@+id/query_text"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_margin="16dp"android:autofillHints="@null"android:background="@drawable/card_background"android:elevation="4dp"android:hint="@string/place_autocomplete_query_hint"android:inputType="text"android:minHeight="?actionBarSize"android:paddingHorizontal="16dp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"/> <com.mapbox.maps.MapViewandroid:id="@+id/map_view"android:layout_width="0dp"android:layout_height="0dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:mapbox_logoGravity="bottom"/> <com.mapbox.search.ui.view.SearchResultsViewandroid:id="@+id/search_results_view"android:layout_width="match_parent"android:layout_height="0dp"android:layout_marginHorizontal="16dp"android:layout_marginTop="16dp"android:background="@drawable/card_background"android:clipToPadding="false"android:elevation="4dp"android:paddingTop="8dp"android:paddingBottom="22dp"android:visibility="gone"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/query_text"/> <androidx.coordinatorlayout.widget.CoordinatorLayoutandroid:id="@+id/search_container_view"android:layout_width="match_parent"android:layout_height="match_parent"android:elevation="@dimen/search_card_elevation"> <com.mapbox.search.ui.view.place.SearchPlaceBottomSheetViewandroid:id="@+id/search_place_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:elevation="@dimen/search_card_elevation"/></androidx.coordinatorlayout.widget.CoordinatorLayout></androidx.constraintlayout.widget.ConstraintLayout>
package com.mapbox.search.sample import android.Manifestimport android.os.Bundleimport android.text.Editableimport android.text.TextWatcherimport android.util.Logimport android.widget.EditTextimport androidx.appcompat.app.AppCompatActivityimport androidx.core.app.ActivityCompatimport androidx.core.view.isVisibleimport androidx.lifecycle.lifecycleScopeimport com.mapbox.android.core.location.LocationEngineProviderimport com.mapbox.geojson.Pointimport com.mapbox.maps.CameraOptionsimport com.mapbox.maps.EdgeInsetsimport com.mapbox.maps.MapViewimport com.mapbox.maps.MapboxMapimport com.mapbox.maps.Styleimport com.mapbox.maps.plugin.annotation.annotationsimport com.mapbox.maps.plugin.annotation.generated.CircleAnnotationOptionsimport com.mapbox.maps.plugin.annotation.generated.createCircleAnnotationManagerimport com.mapbox.maps.plugin.gestures.addOnMapLongClickListenerimport com.mapbox.maps.plugin.locationcomponent.OnIndicatorPositionChangedListenerimport com.mapbox.maps.plugin.locationcomponent.locationimport com.mapbox.search.autocomplete.PlaceAutocompleteimport com.mapbox.search.autocomplete.PlaceAutocompleteOptionsimport com.mapbox.search.autocomplete.PlaceAutocompleteSuggestionimport com.mapbox.search.autocomplete.PlaceAutocompleteTypeimport com.mapbox.search.ui.adapter.autocomplete.PlaceAutocompleteUiAdapterimport com.mapbox.search.ui.view.CommonSearchViewConfigurationimport com.mapbox.search.ui.view.SearchResultsViewimport com.mapbox.search.ui.view.place.SearchPlaceimport com.mapbox.search.ui.view.place.SearchPlaceBottomSheetView class PlaceAutocompleteUiActivity : AppCompatActivity() { private lateinit var placeAutocomplete: PlaceAutocomplete private lateinit var searchResultsView: SearchResultsViewprivate lateinit var placeAutocompleteUiAdapter: PlaceAutocompleteUiAdapter private lateinit var queryEditText: EditText private lateinit var mapView: MapViewprivate lateinit var mapboxMap: MapboxMapprivate lateinit var mapMarkersManager: MapMarkersManager private lateinit var searchPlaceView: SearchPlaceBottomSheetView private var ignoreNextQueryUpdate = false override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_place_autocomplete) placeAutocomplete = PlaceAutocomplete.create(getString(R.string.mapbox_access_token)) queryEditText = findViewById(R.id.query_text) mapView = findViewById(R.id.map_view)mapView.getMapboxMap().also { mapboxMap ->this.mapboxMap = mapboxMap mapboxMap.loadStyleUri(Style.MAPBOX_STREETS) {mapView.location.updateSettings {enabled = true} mapView.location.addOnIndicatorPositionChangedListener(object : OnIndicatorPositionChangedListener {override fun onIndicatorPositionChanged(point: Point) {mapView.getMapboxMap().setCamera(CameraOptions.Builder().center(point).zoom(14.0).build()) mapView.location.removeOnIndicatorPositionChangedListener(this)}})}} mapMarkersManager = MapMarkersManager(mapView) mapboxMap.addOnMapLongClickListener {reverseGeocoding(it)return@addOnMapLongClickListener true} searchResultsView = findViewById(R.id.search_results_view) searchResultsView.initialize(SearchResultsView.Configuration(commonConfiguration = CommonSearchViewConfiguration())) placeAutocompleteUiAdapter = PlaceAutocompleteUiAdapter(view = searchResultsView,placeAutocomplete = placeAutocomplete) searchPlaceView = findViewById<SearchPlaceBottomSheetView>(R.id.search_place_view).apply {initialize(CommonSearchViewConfiguration()) isFavoriteButtonVisible = false addOnCloseClickListener {hide()closePlaceCard()} addOnNavigateClickListener { searchPlace ->startActivity(geoIntent(searchPlace.coordinate))} addOnShareClickListener { searchPlace ->startActivity(shareIntent(searchPlace))}} LocationEngineProvider.getBestLocationEngine(applicationContext).lastKnownLocation(this) { point ->point?.let {mapView.getMapboxMap().setCamera(CameraOptions.Builder().center(point).zoom(9.0).build())}} placeAutocompleteUiAdapter.addSearchListener(object : PlaceAutocompleteUiAdapter.SearchListener { override fun onSuggestionsShown(suggestions: List<PlaceAutocompleteSuggestion>) {// Nothing to do} override fun onSuggestionSelected(suggestion: PlaceAutocompleteSuggestion) {openPlaceCard(suggestion)} override fun onPopulateQueryClick(suggestion: PlaceAutocompleteSuggestion) {queryEditText.setText(suggestion.name)} override fun onError(e: Exception) {// Nothing to do}}) queryEditText.addTextChangedListener(object : TextWatcher { override fun onTextChanged(text: CharSequence, start: Int, before: Int, count: Int) {if (ignoreNextQueryUpdate) {ignoreNextQueryUpdate = false} else {closePlaceCard()} lifecycleScope.launchWhenStarted {placeAutocompleteUiAdapter.search(text.toString())searchResultsView.isVisible = text.isNotEmpty()}} override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {// Nothing to do} override fun afterTextChanged(s: Editable) {// Nothing to do}}) if (!isPermissionGranted(Manifest.permission.ACCESS_FINE_LOCATION)) {ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION),PERMISSIONS_REQUEST_LOCATION)}} private fun reverseGeocoding(point: Point) {val types: List<PlaceAutocompleteType> = when (mapboxMap.cameraState.zoom) {in 0.0..4.0 -> REGION_LEVEL_TYPESin 4.0..6.0 -> DISTRICT_LEVEL_TYPESin 6.0..12.0 -> LOCALITY_LEVEL_TYPESelse -> ALL_TYPES} lifecycleScope.launchWhenStarted {val response = placeAutocomplete.suggestions(point, PlaceAutocompleteOptions(types = types))response.onValue { suggestions ->if (suggestions.isEmpty()) {showToast(R.string.place_autocomplete_reverse_geocoding_error_message)} else {openPlaceCard(suggestions.first())}}.onError { error ->Log.d(LOG_TAG, "Reverse geocoding error", error)showToast(R.string.place_autocomplete_reverse_geocoding_error_message)}}} private fun openPlaceCard(suggestion: PlaceAutocompleteSuggestion) {ignoreNextQueryUpdate = truequeryEditText.setText("") lifecycleScope.launchWhenStarted {placeAutocomplete.select(suggestion).onValue { result ->mapMarkersManager.showMarker(suggestion.coordinate)searchPlaceView.open(SearchPlace.createFromPlaceAutocompleteResult(result))queryEditText.hideKeyboard()searchResultsView.isVisible = false}.onError { error ->Log.d(LOG_TAG, "Suggestion selection error", error)showToast(R.string.place_autocomplete_selection_error)}}} private fun closePlaceCard() {searchPlaceView.hide()mapMarkersManager.clearMarkers()} private class MapMarkersManager(mapView: MapView) { private val mapboxMap = mapView.getMapboxMap()private val circleAnnotationManager = mapView.annotations.createCircleAnnotationManager(null)private val markers = mutableMapOf<Long, Point>() fun clearMarkers() {markers.clear()circleAnnotationManager.deleteAll()} fun showMarker(coordinate: Point) {clearMarkers() val circleAnnotationOptions: CircleAnnotationOptions = CircleAnnotationOptions().withPoint(coordinate).withCircleRadius(8.0).withCircleColor("#ee4e8b").withCircleStrokeWidth(2.0).withCircleStrokeColor("#ffffff") val annotation = circleAnnotationManager.create(circleAnnotationOptions)markers[annotation.id] = coordinate CameraOptions.Builder().center(coordinate).padding(MARKERS_INSETS_OPEN_CARD).zoom(14.0).build().also {mapboxMap.setCamera(it)}}} private companion object { const val PERMISSIONS_REQUEST_LOCATION = 0 const val LOG_TAG = "AutocompleteUiActivity" val MARKERS_EDGE_OFFSET = dpToPx(64).toDouble()val PLACE_CARD_HEIGHT = dpToPx(300).toDouble()val MARKERS_TOP_OFFSET = dpToPx(88).toDouble() val MARKERS_INSETS_OPEN_CARD = EdgeInsets(MARKERS_TOP_OFFSET, MARKERS_EDGE_OFFSET, PLACE_CARD_HEIGHT, MARKERS_EDGE_OFFSET) val REGION_LEVEL_TYPES = listOf(PlaceAutocompleteType.AdministrativeUnit.Country,PlaceAutocompleteType.AdministrativeUnit.Region) val DISTRICT_LEVEL_TYPES = REGION_LEVEL_TYPES + listOf(PlaceAutocompleteType.AdministrativeUnit.Postcode,PlaceAutocompleteType.AdministrativeUnit.District) val LOCALITY_LEVEL_TYPES = DISTRICT_LEVEL_TYPES + listOf(PlaceAutocompleteType.AdministrativeUnit.Place,PlaceAutocompleteType.AdministrativeUnit.Locality) private val ALL_TYPES = listOf(PlaceAutocompleteType.Poi,PlaceAutocompleteType.AdministrativeUnit.Country,PlaceAutocompleteType.AdministrativeUnit.Region,PlaceAutocompleteType.AdministrativeUnit.Postcode,PlaceAutocompleteType.AdministrativeUnit.District,PlaceAutocompleteType.AdministrativeUnit.Place,PlaceAutocompleteType.AdministrativeUnit.Locality,PlaceAutocompleteType.AdministrativeUnit.Neighborhood,PlaceAutocompleteType.AdministrativeUnit.Street,PlaceAutocompleteType.AdministrativeUnit.Address,)}}
Was this example helpful?