Skip to content

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() or VectorSource.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() or resetFeatureStates() on the source to clear state.
  • For VectorSource, all feature-state methods require a sourceLayerId parameter.