Skip to main content

Add interactions to custom featuresets and the map

This example shows how to use addInteraction with a featureset in a custom map style. The hotels-price featureset has been pre-configured to change the color of the icon when setting its selected feature state to true.

The interaction handler sets the feature state and triggers a toast message in the UI. addInteraction is used a second time to handle a click on the map, resetting the feature state and dismissing the toast message.

Android Examples App Available

This example code is part of the Maps SDK for Android Examples App, a working Android project available on GitHub. Android developers are encouraged to run the examples app locally to interact with this example in an emulator and explore other features of the Maps SDK.

See our Run the Maps SDK for Android Examples App tutorial for step-by-step instructions.

InteractionsActivity.kt
package com.mapbox.maps.compose.testapp.examples.style

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.mapbox.geojson.Point
import com.mapbox.maps.EdgeInsets
import com.mapbox.maps.MapboxExperimental
import com.mapbox.maps.compose.testapp.ExampleScaffold
import com.mapbox.maps.compose.testapp.ui.theme.MapboxMapComposeTheme
import com.mapbox.maps.dsl.cameraOptions
import com.mapbox.maps.extension.compose.MapboxMap
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
import com.mapbox.maps.extension.compose.rememberMapState
import com.mapbox.maps.extension.compose.style.GenericStyle
import com.mapbox.maps.extension.compose.style.rememberStyleState
import com.mapbox.maps.interactions.FeatureState
import com.mapbox.maps.interactions.FeatureStateKey
import com.mapbox.maps.interactions.FeaturesetFeature

public class InteractionsActivity : ComponentActivity() {

@OptIn(MapboxExperimental::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {

var selectedPriceLabel by remember {
mutableStateOf<FeaturesetFeature<FeatureState>?>(null)
}

var isActive: Boolean? by remember {
// null as we do not know the initial state
mutableStateOf(null)
}

val mapState = rememberMapState()
val mapViewportState = rememberMapViewportState {
setCameraOptions(
cameraOptions {
center(Point.fromLngLat(-73.99, 40.72))
zoom(11.0)
pitch(45.0)
}
)
}

LaunchedEffect(selectedPriceLabel) {
selectedPriceLabel?.let {
val camera = mapViewportState.cameraForCoordinates(
listOf(it.geometry as Point),
cameraOptions {
padding(
EdgeInsets(100.0, 100.0, 100.0, 100.0)
)
},
maxZoom = 11.0
)
mapViewportState.flyTo(camera)
}
}

MapboxMapComposeTheme {
ExampleScaffold {
Box(modifier = Modifier.fillMaxSize()) {
MapboxMap(
modifier = Modifier.fillMaxSize(),
mapState = mapState,
mapViewportState = mapViewportState,
style = {
GenericStyle(
style = "asset://fragment-realestate-NY.json",
styleState = rememberStyleState {
styleInteractionsState
.onFeaturesetClicked(id = "hotels-price") { priceLabel, _ ->
if (selectedPriceLabel?.id != priceLabel.id) {
selectedPriceLabel?.removeFeatureState(FeatureStateKey.create("active"))
selectedPriceLabel = priceLabel
}
val newActiveState = priceLabel.state.getBooleanState("active")?.not() ?: true
priceLabel.setFeatureState(
FeatureState {
addBooleanState("active", newActiveState)
}
) {
isActive = newActiveState
}
return@onFeaturesetClicked true
}
.onMapClicked {
selectedPriceLabel?.removeFeatureState(FeatureStateKey.create("active"))
selectedPriceLabel = null
isActive = null
true
}
}
)
}
)
selectedPriceLabel?.let { actualSelectedPriceLabel ->
Text(
modifier = Modifier
.wrapContentWidth()
.align(Alignment.BottomCenter)
.padding(40.dp)
.border(2.dp, MaterialTheme.colors.secondary, CircleShape)
.background(MaterialTheme.colors.primary, CircleShape)
.padding(10.dp),
textAlign = TextAlign.Center,
color = Color.White,
text = "Feature with id: ${actualSelectedPriceLabel.id!!}; active: $isActive",
)
}
}
}
}
}
}
}
Was this example helpful?