Basic search + Maps SDK integration
Besides Search SDK and Search UI SDK 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.
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.
<?xml version="1.0" encoding="utf-8"?><FrameLayoutxmlns: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:id="@+id/root"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"tools:ignore="MergeRootFrame"> <com.mapbox.maps.MapViewandroid:id="@+id/map_view"android:layout_width="match_parent"android:layout_height="match_parent"app:mapbox_logoGravity="bottom"/> <androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="wrap_content"android:elevation="4dp"android:theme="@style/ToolbarTheme"/> <com.mapbox.search.ui.view.SearchResultsViewandroid:id="@+id/search_results_view"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="?actionBarSize"android:clipToPadding="false"android:paddingTop="22dp"android:paddingBottom="22dp"/> <androidx.coordinatorlayout.widget.CoordinatorLayoutandroid:id="@+id/search_container_view"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="?actionBarSize"> <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></FrameLayout>
package com.mapbox.search.sample import android.Manifestimport android.content.Intentimport android.content.res.Configurationimport android.os.Bundleimport android.util.Logimport android.view.Menuimport android.view.MenuItemimport android.widget.Toastimport androidx.activity.OnBackPressedCallbackimport androidx.appcompat.app.AppCompatActivityimport androidx.appcompat.widget.SearchViewimport androidx.appcompat.widget.Toolbarimport androidx.core.app.ActivityCompatimport androidx.core.view.isVisibleimport com.mapbox.android.core.location.LocationEngineimport 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.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.locationcomponent.OnIndicatorPositionChangedListenerimport com.mapbox.maps.plugin.locationcomponent.locationimport com.mapbox.search.ApiTypeimport com.mapbox.search.ResponseInfoimport com.mapbox.search.SearchEngineimport com.mapbox.search.SearchEngineSettingsimport com.mapbox.search.offline.OfflineResponseInfoimport com.mapbox.search.offline.OfflineSearchEngineimport com.mapbox.search.offline.OfflineSearchEngineSettingsimport com.mapbox.search.offline.OfflineSearchResultimport com.mapbox.search.record.HistoryRecordimport com.mapbox.search.result.SearchResultimport com.mapbox.search.result.SearchSuggestionimport com.mapbox.search.sample.api.AddressAutofillKotlinExampleActivityimport com.mapbox.search.sample.api.CategorySearchJavaExampleActivityimport com.mapbox.search.sample.api.CategorySearchKotlinExampleActivityimport com.mapbox.search.sample.api.CustomIndexableDataProviderJavaExampleimport com.mapbox.search.sample.api.CustomIndexableDataProviderKotlinExampleimport com.mapbox.search.sample.api.DiscoverJavaExampleActivityimport com.mapbox.search.sample.api.DiscoverKotlinExampleActivityimport com.mapbox.search.sample.api.FavoritesDataProviderJavaExampleimport com.mapbox.search.sample.api.FavoritesDataProviderKotlinExampleimport com.mapbox.search.sample.api.ForwardGeocodingBatchResolvingJavaExampleActivityimport com.mapbox.search.sample.api.ForwardGeocodingBatchResolvingKotlinExampleActivityimport com.mapbox.search.sample.api.ForwardGeocodingJavaExampleActivityimport com.mapbox.search.sample.api.ForwardGeocodingKotlinExampleActivityimport com.mapbox.search.sample.api.HistoryDataProviderJavaExampleimport com.mapbox.search.sample.api.HistoryDataProviderKotlinExampleimport com.mapbox.search.sample.api.JapanSearchJavaExampleActivityimport com.mapbox.search.sample.api.JapanSearchKotlinExampleActivityimport com.mapbox.search.sample.api.OfflineReverseGeocodingJavaExampleActivityimport com.mapbox.search.sample.api.OfflineReverseGeocodingKotlinExampleActivityimport com.mapbox.search.sample.api.OfflineSearchJavaExampleActivityimport com.mapbox.search.sample.api.OfflineSearchKotlinExampleActivityimport com.mapbox.search.sample.api.PlaceAutocompleteKotlinExampleActivityimport com.mapbox.search.sample.api.ReverseGeocodingJavaExampleActivityimport com.mapbox.search.sample.api.ReverseGeocodingKotlinExampleActivityimport com.mapbox.search.ui.adapter.engines.SearchEngineUiAdapterimport com.mapbox.search.ui.view.CommonSearchViewConfigurationimport com.mapbox.search.ui.view.DistanceUnitTypeimport com.mapbox.search.ui.view.SearchModeimport com.mapbox.search.ui.view.SearchResultsViewimport com.mapbox.search.ui.view.place.SearchPlaceimport com.mapbox.search.ui.view.place.SearchPlaceBottomSheetView class MainActivity : AppCompatActivity() { private lateinit var locationEngine: LocationEngine private lateinit var toolbar: Toolbarprivate lateinit var searchView: SearchView private lateinit var searchResultsView: SearchResultsViewprivate lateinit var searchEngineUiAdapter: SearchEngineUiAdapterprivate lateinit var searchPlaceView: SearchPlaceBottomSheetView private lateinit var mapView: MapViewprivate lateinit var mapMarkersManager: MapMarkersManager private val onBackPressedCallback = object : OnBackPressedCallback(false) {override fun handleOnBackPressed() {when {!searchPlaceView.isHidden() -> {mapMarkersManager.clearMarkers()searchPlaceView.hide()}mapMarkersManager.hasMarkers -> {mapMarkersManager.clearMarkers()}else -> {if (BuildConfig.DEBUG) {error("This OnBackPressedCallback should not be enabled")}Log.i("SearchApiExample", "This OnBackPressedCallback should not be enabled")isEnabled = falseonBackPressedDispatcher.onBackPressed()}}}} override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main) onBackPressedDispatcher.addCallback(onBackPressedCallback) locationEngine = LocationEngineProvider.getBestLocationEngine(applicationContext) mapView = findViewById(R.id.map_view)mapView.getMapboxMap().also { mapboxMap ->mapboxMap.loadStyleUri(getMapStyleUri()) 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)mapMarkersManager.onMarkersChangeListener = {updateOnBackPressedCallbackEnabled()} toolbar = findViewById(R.id.toolbar)toolbar.apply {title = getString(R.string.simple_ui_toolbar_title)setSupportActionBar(this)} val apiType = if (BuildConfig.ENABLE_SBS) {ApiType.SBS} else {ApiType.GEOCODING} searchResultsView = findViewById<SearchResultsView>(R.id.search_results_view).apply {initialize(SearchResultsView.Configuration(CommonSearchViewConfiguration(DistanceUnitType.IMPERIAL)))isVisible = false} val searchEngine = SearchEngine.createSearchEngineWithBuiltInDataProviders(apiType = apiType,settings = SearchEngineSettings(getString(R.string.mapbox_access_token))) val offlineSearchEngine = OfflineSearchEngine.create(OfflineSearchEngineSettings(getString(R.string.mapbox_access_token))) searchEngineUiAdapter = SearchEngineUiAdapter(view = searchResultsView,searchEngine = searchEngine,offlineSearchEngine = offlineSearchEngine,) searchEngineUiAdapter.searchMode = SearchMode.AUTO searchEngineUiAdapter.addSearchListener(object : SearchEngineUiAdapter.SearchListener { override fun onSuggestionsShown(suggestions: List<SearchSuggestion>, responseInfo: ResponseInfo) {// Nothing to do} override fun onCategoryResultsShown(suggestion: SearchSuggestion,results: List<SearchResult>,responseInfo: ResponseInfo) {closeSearchView()mapMarkersManager.showMarkers(results.map { it.coordinate })} override fun onOfflineSearchResultsShown(results: List<OfflineSearchResult>, responseInfo: OfflineResponseInfo) {// Nothing to do} override fun onSuggestionSelected(searchSuggestion: SearchSuggestion): Boolean {return false} override fun onSearchResultSelected(searchResult: SearchResult, responseInfo: ResponseInfo) {closeSearchView()searchPlaceView.open(SearchPlace.createFromSearchResult(searchResult, responseInfo))mapMarkersManager.showMarker(searchResult.coordinate)} override fun onOfflineSearchResultSelected(searchResult: OfflineSearchResult, responseInfo: OfflineResponseInfo) {closeSearchView()searchPlaceView.open(SearchPlace.createFromOfflineSearchResult(searchResult))mapMarkersManager.showMarker(searchResult.coordinate)} override fun onError(e: Exception) {Toast.makeText(applicationContext, "Error happened: $e", Toast.LENGTH_SHORT).show()} override fun onHistoryItemClick(historyRecord: HistoryRecord) {closeSearchView()searchPlaceView.open(SearchPlace.createFromIndexableRecord(historyRecord, distanceMeters = null)) locationEngine.userDistanceTo(this@MainActivity, historyRecord.coordinate) { distance ->distance?.let {searchPlaceView.updateDistance(distance)}} mapMarkersManager.showMarker(historyRecord.coordinate)} override fun onPopulateQueryClick(suggestion: SearchSuggestion, responseInfo: ResponseInfo) {if (::searchView.isInitialized) {searchView.setQuery(suggestion.name, true)}} override fun onFeedbackItemClick(responseInfo: ResponseInfo) {// Not implemented}}) searchPlaceView = findViewById(R.id.search_place_view)searchPlaceView.initialize(CommonSearchViewConfiguration(DistanceUnitType.IMPERIAL)) searchPlaceView.addOnCloseClickListener {mapMarkersManager.clearMarkers()searchPlaceView.hide()} searchPlaceView.addOnNavigateClickListener { searchPlace ->startActivity(geoIntent(searchPlace.coordinate))} searchPlaceView.addOnShareClickListener { searchPlace ->startActivity(shareIntent(searchPlace))} searchPlaceView.addOnFeedbackClickListener { _, _ ->// Not implemented} searchPlaceView.addOnBottomSheetStateChangedListener { _, _ ->updateOnBackPressedCallbackEnabled()} 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 updateOnBackPressedCallbackEnabled() {onBackPressedCallback.isEnabled = !searchPlaceView.isHidden() || mapMarkersManager.hasMarkers} private fun closeSearchView() {toolbar.collapseActionView()searchView.setQuery("", false)} override fun onCreateOptionsMenu(menu: Menu): Boolean {menuInflater.inflate(R.menu.main_activity_options_menu, menu) val searchActionView = menu.findItem(R.id.action_search)searchActionView.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {override fun onMenuItemActionExpand(item: MenuItem): Boolean {searchPlaceView.hide()searchResultsView.isVisible = truereturn true} override fun onMenuItemActionCollapse(item: MenuItem): Boolean {searchResultsView.isVisible = falsereturn true}}) searchView = searchActionView.actionView as SearchViewsearchView.queryHint = getString(R.string.query_hint)searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {override fun onQueryTextSubmit(query: String): Boolean {return false} override fun onQueryTextChange(newText: String): Boolean {searchEngineUiAdapter.search(newText)return false}})return true} override fun onOptionsItemSelected(item: MenuItem): Boolean {return when (item.itemId) {R.id.open_address_autofill_ui_example -> {startActivity(Intent(this, AddressAutofillUiActivity::class.java))true}R.id.open_address_autofill_example -> {startActivity(Intent(this, AddressAutofillKotlinExampleActivity::class.java))true}R.id.open_discover_ui_example -> {startActivity(Intent(this, DiscoverActivity::class.java))true}R.id.open_discover_kotlin_example -> {startActivity(Intent(this, DiscoverKotlinExampleActivity::class.java))true}R.id.open_discover_java_example -> {startActivity(Intent(this, DiscoverJavaExampleActivity::class.java))true}R.id.open_place_autocomplete_ui_example -> {startActivity(Intent(this, PlaceAutocompleteUiActivity::class.java))true}R.id.open_place_autocomplete_kotlin_example -> {startActivity(Intent(this, PlaceAutocompleteKotlinExampleActivity::class.java))true}R.id.open_custom_data_provider_kt_example -> {startActivity(Intent(this, CustomIndexableDataProviderKotlinExample::class.java))true}R.id.open_custom_data_provider_java_example -> {startActivity(Intent(this, CustomIndexableDataProviderJavaExample::class.java))true}R.id.custom_theme_example -> {startActivity(Intent(this, CustomThemeActivity::class.java))true}R.id.open_forward_geocoding_kt_example -> {startActivity(Intent(this, ForwardGeocodingKotlinExampleActivity::class.java))true}R.id.open_forward_geocoding_java_example -> {startActivity(Intent(this, ForwardGeocodingJavaExampleActivity::class.java))true}R.id.open_forward_geocoding_batch_resolving_kt_example -> {startActivity(Intent(this, ForwardGeocodingBatchResolvingKotlinExampleActivity::class.java))true}R.id.open_forward_geocoding_batch_resolving_java_example -> {startActivity(Intent(this, ForwardGeocodingBatchResolvingJavaExampleActivity::class.java))true}R.id.open_reverse_geocoding_kt_example -> {startActivity(Intent(this, ReverseGeocodingKotlinExampleActivity::class.java))true}R.id.open_reverse_geocoding_java_example -> {startActivity(Intent(this, ReverseGeocodingJavaExampleActivity::class.java))true}R.id.open_japan_search_kt_example -> {startActivity(Intent(this, JapanSearchKotlinExampleActivity::class.java))true}R.id.open_japan_search_java_example -> {startActivity(Intent(this, JapanSearchJavaExampleActivity::class.java))true}R.id.open_category_search_kt_example -> {startActivity(Intent(this, CategorySearchKotlinExampleActivity::class.java))true}R.id.open_category_search_java_example -> {startActivity(Intent(this, CategorySearchJavaExampleActivity::class.java))true}R.id.open_offline_search_java_example -> {startActivity(Intent(this, OfflineSearchJavaExampleActivity::class.java))true}R.id.open_offline_search_kt_example -> {startActivity(Intent(this, OfflineSearchKotlinExampleActivity::class.java))true}R.id.open_offline_reverse_geocoding_java_example -> {startActivity(Intent(this, OfflineReverseGeocodingJavaExampleActivity::class.java))true}R.id.open_offline_reverse_geocoding_kt_example -> {startActivity(Intent(this, OfflineReverseGeocodingKotlinExampleActivity::class.java))true}R.id.open_history_data_provider_java_example -> {startActivity(Intent(this, HistoryDataProviderJavaExample::class.java))true}R.id.open_history_data_provider_kt_example -> {startActivity(Intent(this, HistoryDataProviderKotlinExample::class.java))true}R.id.open_favorites_data_provider_java_example -> {startActivity(Intent(this, FavoritesDataProviderJavaExample::class.java))true}R.id.open_favorites_data_provider_kt_example -> {startActivity(Intent(this, FavoritesDataProviderKotlinExample::class.java))true}else -> super.onOptionsItemSelected(item)}} private fun getMapStyleUri(): String {return when (val darkMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {Configuration.UI_MODE_NIGHT_YES -> Style.DARKConfiguration.UI_MODE_NIGHT_NO,Configuration.UI_MODE_NIGHT_UNDEFINED -> Style.MAPBOX_STREETSelse -> error("Unknown mode: $darkMode")}} private class MapMarkersManager(mapView: MapView) { private val mapboxMap = mapView.getMapboxMap()private val circleAnnotationManager = mapView.annotations.createCircleAnnotationManager(null)private val markers = mutableMapOf<Long, Point>() var onMarkersChangeListener: (() -> Unit)? = null val hasMarkers: Booleanget() = markers.isNotEmpty() fun clearMarkers() {markers.clear()circleAnnotationManager.deleteAll()} fun showMarker(coordinate: Point) {showMarkers(listOf(coordinate))} fun showMarkers(coordinates: List<Point>) {clearMarkers()if (coordinates.isEmpty()) {onMarkersChangeListener?.invoke()return} coordinates.forEach { coordinate ->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} if (coordinates.size == 1) {CameraOptions.Builder().center(coordinates.first()).padding(MARKERS_INSETS_OPEN_CARD).zoom(14.0).build()} else {mapboxMap.cameraForCoordinates(coordinates, MARKERS_INSETS, bearing = null, pitch = null)}.also {mapboxMap.setCamera(it)}onMarkersChangeListener?.invoke()}} private companion object { val MARKERS_EDGE_OFFSET = dpToPx(64).toDouble()val PLACE_CARD_HEIGHT = dpToPx(300).toDouble() val MARKERS_INSETS = EdgeInsets(MARKERS_EDGE_OFFSET, MARKERS_EDGE_OFFSET, MARKERS_EDGE_OFFSET, MARKERS_EDGE_OFFSET) val MARKERS_INSETS_OPEN_CARD = EdgeInsets(MARKERS_EDGE_OFFSET, MARKERS_EDGE_OFFSET, PLACE_CARD_HEIGHT, MARKERS_EDGE_OFFSET) const val PERMISSIONS_REQUEST_LOCATION = 0}}