Skip to main content

Discover + 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. The dependencies can be found here.The examples use View binding.See setup documention if necessary.

Besides Discover 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.

activity_discover
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<com.mapbox.maps.MapView
android: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"
/>

<Button
android:id="@+id/search_nearby"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginBottom="16dp"
android:background="@drawable/card_background"
android:text="@string/discover_search_nearby"
app:layout_constraintBottom_toTopOf="@+id/search_this_area"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>

<Button
android:id="@+id/search_this_area"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="32dp"
android:background="@drawable/card_background"
android:text="@string/discover_search_in_area"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>

<androidx.coordinatorlayout.widget.CoordinatorLayout
android: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.SearchPlaceBottomSheetView
android: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>
DiscoverActivity.kt
package com.mapbox.search.sample

import android.Manifest
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.lifecycle.lifecycleScope
import com.mapbox.android.core.location.LocationEngine
import com.mapbox.android.core.location.LocationEngineProvider
import com.mapbox.geojson.Point
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.EdgeInsets
import com.mapbox.maps.MapView
import com.mapbox.maps.MapboxMap
import com.mapbox.maps.Style
import com.mapbox.maps.extension.style.layers.properties.generated.IconAnchor
import com.mapbox.maps.plugin.annotation.annotations
import com.mapbox.maps.plugin.annotation.generated.PointAnnotationOptions
import com.mapbox.maps.plugin.annotation.generated.createPointAnnotationManager
import com.mapbox.maps.plugin.locationcomponent.OnIndicatorPositionChangedListener
import com.mapbox.maps.plugin.locationcomponent.location
import com.mapbox.search.discover.Discover
import com.mapbox.search.discover.DiscoverAddress
import com.mapbox.search.discover.DiscoverOptions
import com.mapbox.search.discover.DiscoverQuery
import com.mapbox.search.discover.DiscoverResult
import com.mapbox.search.result.SearchAddress
import com.mapbox.search.result.SearchResultType
import com.mapbox.search.ui.view.CommonSearchViewConfiguration
import com.mapbox.search.ui.view.DistanceUnitType
import com.mapbox.search.ui.view.place.SearchPlace
import com.mapbox.search.ui.view.place.SearchPlaceBottomSheetView
import java.util.UUID

class DiscoverActivity : AppCompatActivity() {

private lateinit var discover: Discover
private lateinit var locationEngine: LocationEngine

private lateinit var mapView: MapView
private lateinit var mapboxMap: MapboxMap
private lateinit var mapMarkersManager: MapMarkersManager

private lateinit var searchNearby: View
private lateinit var searchThisArea: View

private lateinit var searchPlaceView: SearchPlaceBottomSheetView

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

discover = Discover.create(getString(R.string.mapbox_access_token))
locationEngine = LocationEngineProvider.getBestLocationEngine(applicationContext)

mapView = findViewById(R.id.map_view)
mapMarkersManager = MapMarkersManager(mapView)
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)
}
})
}
}

searchNearby = findViewById(R.id.search_nearby)
searchNearby.setOnClickListener {
locationEngine.lastKnownLocation(this) { location ->
if (location == null) {
return@lastKnownLocation
}

lifecycleScope.launchWhenStarted {
val response = discover.search(
query = DiscoverQuery.Category.COFFEE_SHOP_CAFE,
proximity = location,
options = DiscoverOptions(limit = 20)
)

response.onValue { results ->
mapMarkersManager.showResults(results)
}.onError { e ->
Log.d("DiscoverApiExample", "Error happened during search request", e)
showToast(R.string.discover_search_error)
}
}
}
}

searchThisArea = findViewById(R.id.search_this_area)
searchThisArea.setOnClickListener {
lifecycleScope.launchWhenStarted {
val response = discover.search(
query = DiscoverQuery.Category.COFFEE_SHOP_CAFE,
region = mapboxMap.getCameraBoundingBox(),
options = DiscoverOptions(limit = 20)
)

response.onValue { results ->
mapMarkersManager.showResults(results)
}.onError { e ->
Log.d("DiscoverApiExample", "Error happened during search request", e)
showToast(R.string.discover_search_error)
}
}
}

searchPlaceView = findViewById<SearchPlaceBottomSheetView>(R.id.search_place_view).apply {
initialize(CommonSearchViewConfiguration(DistanceUnitType.IMPERIAL))
isFavoriteButtonVisible = false
addOnCloseClickListener {
mapMarkersManager.adjustMarkersForClosedCard()
searchPlaceView.hide()
}
}

mapMarkersManager.onResultClickListener = { result ->
mapMarkersManager.adjustMarkersForOpenCard()
searchPlaceView.open(result.toSearchPlace())
locationEngine.userDistanceTo(this@DiscoverActivity, result.coordinate) { distance ->
distance?.let { searchPlaceView.updateDistance(distance) }
}
}

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 class MapMarkersManager(mapView: MapView) {

private val annotations = mutableMapOf<Long, DiscoverResult>()
private val mapboxMap: MapboxMap = mapView.getMapboxMap()
private val pointAnnotationManager = mapView.annotations.createPointAnnotationManager(null)
private val pinBitmap = mapView.context.bitmapFromDrawableRes(R.drawable.red_marker)

var onResultClickListener: ((DiscoverResult) -> Unit)? = null

init {
pointAnnotationManager.addClickListener {
annotations[it.id]?.let { result ->
onResultClickListener?.invoke(result)
}
true
}
}

fun clearMarkers() {
pointAnnotationManager.deleteAll()
annotations.clear()
}

fun adjustMarkersForOpenCard() {
val coordinates = annotations.values.map { it.coordinate }
val cameraOptions = mapboxMap.cameraForCoordinates(
coordinates, MARKERS_INSETS_OPEN_CARD, bearing = null, pitch = null
)
mapboxMap.setCamera(cameraOptions)
}

fun adjustMarkersForClosedCard() {
val coordinates = annotations.values.map { it.coordinate }
val cameraOptions = mapboxMap.cameraForCoordinates(
coordinates, MARKERS_INSETS, bearing = null, pitch = null
)
mapboxMap.setCamera(cameraOptions)
}

fun showResults(results: List<DiscoverResult>) {
clearMarkers()
if (results.isEmpty() || pinBitmap == null) {
return
}

val coordinates = ArrayList<Point>(results.size)
results.forEach { result ->
val options = PointAnnotationOptions()
.withPoint(result.coordinate)
.withIconImage(pinBitmap)
.withIconAnchor(IconAnchor.BOTTOM)

val annotation = pointAnnotationManager.create(options)
annotations[annotation.id] = result
coordinates.add(result.coordinate)
}

val cameraOptions = mapboxMap.cameraForCoordinates(
coordinates, MARKERS_INSETS, bearing = null, pitch = null
)
mapboxMap.setCamera(cameraOptions)
}
}

private companion object {

const val PERMISSIONS_REQUEST_LOCATION = 0

val MARKERS_BOTTOM_OFFSET = dpToPx(176).toDouble()
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_BOTTOM_OFFSET, MARKERS_EDGE_OFFSET
)

val MARKERS_INSETS_OPEN_CARD = EdgeInsets(
MARKERS_EDGE_OFFSET, MARKERS_EDGE_OFFSET, PLACE_CARD_HEIGHT, MARKERS_EDGE_OFFSET
)

fun DiscoverAddress.toSearchAddress(): SearchAddress {
return SearchAddress(
houseNumber = houseNumber,
street = street,
neighborhood = neighborhood,
locality = locality,
postcode = postcode,
place = place,
district = district,
region = region,
country = country
)
}

fun DiscoverResult.toSearchPlace(): SearchPlace {
return SearchPlace(
id = name + UUID.randomUUID().toString(),
name = name,
descriptionText = null,
address = address.toSearchAddress(),
resultTypes = listOf(SearchResultType.POI),
record = null,
coordinate = coordinate,
routablePoints = routablePoints,
categories = categories,
makiIcon = makiIcon,
metadata = null,
distanceMeters = null,
feedback = null,
)
}
}
}
Was this example helpful?