Beta
Search SDK for Android
All docsSearch SDK for AndroidExamplesAddress Autofill + Search UI SDK + Maps SDK

Address Autofill + Search UI SDK + 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 Search SDK this example also uses 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.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.mapbox.android.core.location.LocationEngine
import com.mapbox.android.core.location.LocationEngineCallback
import com.mapbox.android.core.location.LocationEngineProvider
import com.mapbox.android.core.location.LocationEngineResult
import com.mapbox.android.core.permissions.PermissionsManager
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.AddressAutofillResponse
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 searchEngineUiAdapter: 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)
)
)
searchEngineUiAdapter = AddressAutofillUiAdapter(
view = searchResultsView,
addressAutofill = addressAutofill
)
LocationEngineProvider.getBestLocationEngine(applicationContext).lastKnownLocationOrNull(this) { point ->
point?.let {
mapView.getMapboxMap().setCamera(
CameraOptions.Builder()
.center(point)
.zoom(9.0)
.build()
)
ignoreNextMapIdleEvent = true
}
}
searchEngineUiAdapter.addSearchListener(object : AddressAutofillUiAdapter.SearchListener {
override fun onSuggestionSelected(suggestion: AddressAutofillSuggestion) {
showAddressAutofillSuggestion(
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 {
searchEngineUiAdapter.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 {
when (val response = addressAutofill.suggestions(point, AddressAutofillOptions())) {
is AddressAutofillResponse.Suggestions -> {
if (response.suggestions.isEmpty()) {
showToast(R.string.address_autofill_error_pin_correction)
} else {
showAddressAutofillSuggestion(
response.suggestions.first(),
fromReverseGeocoding = true
)
}
}
is AddressAutofillResponse.Error -> {
val error = response.error
Log.d("Test.", "Test. $error", error)
showToast(R.string.address_autofill_error_pin_correction)
}
}
}
}
private fun showAddressAutofillSuggestion(suggestion: AddressAutofillSuggestion, fromReverseGeocoding: Boolean) {
val address = suggestion.result().address
cityEditText.setText(address.place)
stateEditText.setText(address.region)
zipEditText.setText(address.postcode)
fullAddress.isVisible = true
fullAddress.text = suggestion.formattedAddress
pinCorrectionNote.isVisible = true
if (!fromReverseGeocoding) {
mapView.getMapboxMap().setCamera(
CameraOptions.Builder()
.center(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 fun showToast(@StringRes resId: Int) {
Toast.makeText(applicationContext, getString(resId), Toast.LENGTH_SHORT).show()
}
private companion object {
const val PERMISSIONS_REQUEST_LOCATION = 0
fun Context.isPermissionGranted(permission: String): Boolean {
return ContextCompat.checkSelfPermission(
this, permission
) == PackageManager.PERMISSION_GRANTED
}
val Context.inputMethodManager: InputMethodManager
get() = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
fun View.hideKeyboard() {
context.inputMethodManager.hideSoftInputFromWindow(windowToken, 0)
}
@SuppressLint("MissingPermission")
fun LocationEngine.lastKnownLocationOrNull(context: Context, callback: (Point?) -> Unit) {
if (!PermissionsManager.areLocationPermissionsGranted(context)) {
callback(null)
}
val locationCallback = object : LocationEngineCallback<LocationEngineResult> {
override fun onSuccess(result: LocationEngineResult?) {
val location = (result?.locations?.lastOrNull() ?: result?.lastLocation)?.let { location ->
Point.fromLngLat(location.longitude, location.latitude)
}
callback(location)
}
override fun onFailure(exception: Exception) {
callback(null)
}
}
getLastLocation(locationCallback)
}
}
}