Skip to main content

Address Autofill + 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 Address Autofill 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.

activity_address_autofill
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:background="@color/windowBackground"
tools:context=".AddressAutofillUiActivity"
>

<EditText
android: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/address_autofill_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"
/>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:background="@drawable/card_background"
android:elevation="4dp"
android:padding="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/query_text"
>

<EditText
android:id="@+id/address_city"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autofillHints="postalAddress"
android:hint="@string/address_autofill_address_city_hint"
android:inputType="textPostalAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>

<EditText
android:id="@+id/address_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autofillHints="postalAddress"
android:hint="@string/address_autofill_address_state_hint"
android:inputType="textPostalAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/address_city"
/>

<EditText
android:id="@+id/address_zip"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autofillHints="postalAddress"
android:hint="@string/address_autofill_address_zip_hint"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/address_state"
/>

<EditText
android:id="@+id/address_apartment"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:autofillHints="postalAddress"
android:hint="@string/address_autofill_address_apt_hint"
android:inputType="textPostalAddress"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/address_zip"
/>

<TextView
android:id="@+id/full_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="16dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/address_apartment"
tools:visibility="visible"
/>

<TextView
android:id="@+id/pin_correction_note"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
android:text="@string/address_autofill_pin_correction_note"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/full_address"
tools:visibility="visible"
/>

<com.mapbox.maps.MapView
android:id="@+id/map"
android:layout_width="0dp"
android:layout_height="150dp"
android:layout_margin="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pin_correction_note"
/>

<ImageView
android:id="@+id/map_pin"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:adjustViewBounds="true"
android:src="@drawable/red_marker"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/map_center"
app:layout_constraintEnd_toEndOf="@+id/map"
app:layout_constraintStart_toStartOf="@+id/map"
/>

<Space
android:id="@+id/map_center"
android:layout_width="1dp"
android:layout_height="1dp"
app:layout_constraintBottom_toBottomOf="@+id/map"
app:layout_constraintEnd_toEndOf="@+id/map"
app:layout_constraintStart_toStartOf="@+id/map"
app:layout_constraintTop_toTopOf="@+id/map"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

<com.mapbox.search.ui.view.SearchResultsView
android: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.constraintlayout.widget.ConstraintLayout>
AddressAutofillUiActivity.kt
package com.mapbox.search.sample

import android.Manifest
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.mapbox.android.core.location.LocationEngineProvider
import com.mapbox.geojson.Point
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.MapView
import com.mapbox.maps.MapboxMap
import com.mapbox.maps.Style
import com.mapbox.search.autofill.AddressAutofill
import com.mapbox.search.autofill.AddressAutofillOptions
import com.mapbox.search.autofill.AddressAutofillResult
import com.mapbox.search.autofill.AddressAutofillSuggestion
import com.mapbox.search.autofill.Query
import com.mapbox.search.ui.adapter.autofill.AddressAutofillUiAdapter
import com.mapbox.search.ui.view.CommonSearchViewConfiguration
import com.mapbox.search.ui.view.DistanceUnitType
import com.mapbox.search.ui.view.SearchResultsView

class AddressAutofillUiActivity : AppCompatActivity() {

private lateinit var addressAutofill: AddressAutofill

private lateinit var searchResultsView: SearchResultsView
private lateinit var addressAutofillUiAdapter: AddressAutofillUiAdapter

private lateinit var queryEditText: EditText

private lateinit var apartmentEditText: EditText
private lateinit var cityEditText: EditText
private lateinit var stateEditText: EditText
private lateinit var zipEditText: EditText
private lateinit var fullAddress: TextView
private lateinit var pinCorrectionNote: TextView
private lateinit var mapView: MapView
private lateinit var mapPin: View
private lateinit var mapboxMap: MapboxMap

private var ignoreNextMapIdleEvent: Boolean = false
private var ignoreNextQueryTextUpdate: Boolean = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_address_autofill)

addressAutofill = AddressAutofill.create(getString(R.string.mapbox_access_token))

queryEditText = findViewById(R.id.query_text)
apartmentEditText = findViewById(R.id.address_apartment)
cityEditText = findViewById(R.id.address_city)
stateEditText = findViewById(R.id.address_state)
zipEditText = findViewById(R.id.address_zip)
fullAddress = findViewById(R.id.full_address)
pinCorrectionNote = findViewById(R.id.pin_correction_note)

mapPin = findViewById(R.id.map_pin)
mapView = findViewById(R.id.map)
mapboxMap = mapView.getMapboxMap()
mapboxMap.loadStyleUri(Style.MAPBOX_STREETS)
mapboxMap.addOnMapIdleListener {
if (ignoreNextMapIdleEvent) {
ignoreNextMapIdleEvent = false
return@addOnMapIdleListener
}

val mapCenter = mapboxMap.cameraState.center
findAddress(mapCenter)
}

searchResultsView = findViewById(R.id.search_results_view)

searchResultsView.initialize(
SearchResultsView.Configuration(
commonConfiguration = CommonSearchViewConfiguration(DistanceUnitType.IMPERIAL)
)
)

addressAutofillUiAdapter = AddressAutofillUiAdapter(
view = searchResultsView,
addressAutofill = addressAutofill
)

LocationEngineProvider.getBestLocationEngine(applicationContext).lastKnownLocation(this) { point ->
point?.let {
mapView.getMapboxMap().setCamera(
CameraOptions.Builder()
.center(point)
.zoom(9.0)
.build()
)
ignoreNextMapIdleEvent = true
}
}

addressAutofillUiAdapter.addSearchListener(object : AddressAutofillUiAdapter.SearchListener {

override fun onSuggestionSelected(suggestion: AddressAutofillSuggestion) {
selectSuggestion(
suggestion,
fromReverseGeocoding = false,
)
}

override fun onSuggestionsShown(suggestions: List<AddressAutofillSuggestion>) {
// Nothing to do
}

override fun onError(e: Exception) {
// Nothing to do
}
})

queryEditText.addTextChangedListener(object : TextWatcher {

override fun onTextChanged(text: CharSequence, start: Int, before: Int, count: Int) {
if (ignoreNextQueryTextUpdate) {
ignoreNextQueryTextUpdate = false
return
}

val query = Query.create(text.toString())
if (query != null) {
lifecycleScope.launchWhenStarted {
addressAutofillUiAdapter.search(query)
}
}
searchResultsView.isVisible = query != null
}

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 findAddress(point: Point) {
lifecycleScope.launchWhenStarted {
val response = addressAutofill.suggestions(point, AddressAutofillOptions())
response.onValue { suggestions ->
if (suggestions.isEmpty()) {
showToast(R.string.address_autofill_error_pin_correction)
} else {
selectSuggestion(
suggestions.first(),
fromReverseGeocoding = true
)
}
}.onError {
showToast(R.string.address_autofill_error_pin_correction)
}
}
}

private fun selectSuggestion(suggestion: AddressAutofillSuggestion, fromReverseGeocoding: Boolean) {
lifecycleScope.launchWhenStarted {
val response = addressAutofill.select(suggestion)
response.onValue { result ->
showAddressAutofillResult(result, fromReverseGeocoding)
}.onError {
showToast(R.string.address_autofill_error_select)
}
}
}

private fun showAddressAutofillResult(result: AddressAutofillResult, fromReverseGeocoding: Boolean) {
val address = result.address
cityEditText.setText(address.place)
stateEditText.setText(address.region)
zipEditText.setText(address.postcode)

fullAddress.isVisible = true
fullAddress.text = result.suggestion.formattedAddress

pinCorrectionNote.isVisible = true

if (!fromReverseGeocoding) {
mapView.getMapboxMap().setCamera(
CameraOptions.Builder()
.center(result.suggestion.coordinate)
.zoom(16.0)
.build()
)
ignoreNextMapIdleEvent = true
mapPin.isVisible = true
}

ignoreNextQueryTextUpdate = true
queryEditText.setText(
listOfNotNull(
address.houseNumber,
address.street
).joinToString()
)
queryEditText.clearFocus()

searchResultsView.isVisible = false
searchResultsView.hideKeyboard()
}

private companion object {
const val PERMISSIONS_REQUEST_LOCATION = 0
}
}
Was this example helpful?