All docschevron-rightSearch SDK for Androidchevron-rightarrow-leftExampleschevron-rightDiscover API SDK + Search UI SDK + Maps SDK

Discover API SDK + Search UI SDK + Maps SDK

book
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 API 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.

activity_discover_api
<?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_api_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_api_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>
DiscoverApiUiActivity.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.DiscoverApi
import com.mapbox.search.discover.DiscoverApiAddress
import com.mapbox.search.discover.DiscoverApiOptions
import com.mapbox.search.discover.DiscoverApiQuery
import com.mapbox.search.discover.DiscoverApiResult
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 DiscoverApiUiActivity : AppCompatActivity() {
private lateinit var discoverApi: DiscoverApi
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_api)
discoverApi = DiscoverApi.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 = discoverApi.search(
query = DiscoverApiQuery.Category.COFFEE_SHOP_CAFE,
proximity = location,
options = DiscoverApiOptions(limit = 20)
)
response.onValue { results ->
mapMarkersManager.showResults(results)
}.onError { e ->
Log.d("DiscoverApiExample", "Error happened during search request", e)
showToast(R.string.discover_api_search_error)
}
}
}
}
searchThisArea = findViewById(R.id.search_this_area)
searchThisArea.setOnClickListener {
lifecycleScope.launchWhenStarted {
val response = discoverApi.search(
query = DiscoverApiQuery.Category.COFFEE_SHOP_CAFE,
region = mapboxMap.getCameraBoundingBox(),
options = DiscoverApiOptions(limit = 20)
)
response.onValue { results ->
mapMarkersManager.showResults(results)
}.onError { e ->
Log.d("DiscoverApiExample", "Error happened during search request", e)
showToast(R.string.discover_api_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@DiscoverApiUiActivity, 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, DiscoverApiResult>()
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: ((DiscoverApiResult) -> 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<DiscoverApiResult>) {
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 DiscoverApiAddress.toSearchAddress(): SearchAddress {
return SearchAddress(
houseNumber = houseNumber,
street = street,
neighborhood = neighborhood,
locality = locality,
postcode = postcode,
place = place,
district = district,
region = region,
country = country
)
}
fun DiscoverApiResult.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,
)
}
}
}