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.

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?