Feature State
Note
You can find the full source code of this example in FeatureStateActivity.kt of the MapLibreAndroidTestApp.
This example shows how to use feature-state for interactive styling on Android.
Feature state lets you attach runtime JSON values to individual features, then read those values back inside style expressions. This is useful for interaction-driven UI such as selection, highlighting, or temporary analysis state.
Load US states from a remote GeoJSON source
The example loads the same US states GeoJSON used by the GLFW and iOS demos:
private fun addStatesLayer(style: Style) {
val source = GeoJsonSource(STATES_SOURCE_ID, URI(STATES_URL))
style.addSource(source)
statesSource = source
val selectedFill = Color.parseColor("#d94d41")
val defaultFill = Color.parseColor("#2f7de1")
val selectedBorder = Color.parseColor("#8f241f")
val defaultBorder = Color.parseColor("#2f7de1")
style.addLayer(
FillLayer(FILL_LAYER_ID, STATES_SOURCE_ID).withProperties(
fillColor(
switchCase(
toBool(featureState("selected")),
color(selectedFill),
color(defaultFill)
)
),
fillOpacity(
switchCase(
toBool(featureState("selected")),
literal(0.7f),
literal(0.5f)
)
)
)
)
style.addLayer(
LineLayer(LINE_LAYER_ID, STATES_SOURCE_ID).withProperties(
lineColor(
switchCase(
toBool(featureState("selected")),
color(selectedBorder),
color(defaultBorder)
)
),
lineWidth(
switchCase(
toBool(featureState("selected")),
literal(2.0f),
literal(1.0f)
)
)
)
)
}
Style layers with feature-state expressions
The fill color/opacity and border color/width respond to a selected boolean stored in feature state:
- Selected — red fill, higher opacity, thicker red border
- Default — blue fill, semi-transparent, thin blue border
Toggle selection on tap
Tapping a state toggles its selected feature-state key and shows a toast:
private fun handleMapClick(point: LatLng): Boolean {
val screenPoint: PointF = maplibreMap.projection.toScreenLocation(point)
val features = maplibreMap.queryRenderedFeatures(screenPoint, FILL_LAYER_ID)
val feature = features.firstOrNull() ?: return false
val featureId = feature.id() ?: return false
val currentState = statesSource.getFeatureState(featureId)
val isSelected = currentState
?.get("selected")
?.takeUnless { it.isJsonNull }
?.asBoolean
?: false
val nextState = JsonObject().apply { addProperty("selected", !isSelected) }
statesSource.setFeatureState(featureId, nextState)
val stateName = feature.getStringProperty("STATE_NAME") ?: "Unknown"
Toast.makeText(
this,
"$stateName ${if (!isSelected) "selected" else "deselected"}",
Toast.LENGTH_SHORT
).show()
return true
}
Notes
- Use
GeoJsonSource.setFeatureState()orVectorSource.setFeatureState()to merge new keys into a feature's state. - Use
getFeatureState()on the source to inspect the current state for one feature. - Use
removeFeatureState()orresetFeatureStates()on the source to clear state. - For
VectorSource, all feature-state methods require asourceLayerIdparameter.