← Back to garden 🚀

output

Displaying and Updating GeoJSON on a MapBox in Jetpack Compose

Context

Recently, I needed to add a 3D World Globe Map in Android for my Chrono Guides project, and while I was able to successfully display GeoJSON features and polygons on the map, the data source didn’t seem to update. So today, I’ll share how to update the Map GeoJSON with Compose’s state.

If you have already setup Mapbox, you can skip to the section “Dynamically updating GeoJSON.”

Setting up MapBox in Compose (Multiplatform)

We will cover how to setup MapBox compose for Android in a multiplatform project as it was my case, although I will skip the credentials setup step since that is covered in the MapBox documentation
1.- Install dependencies

# libs.versions.toml
[versions]
mapbox= "11.7.2"
[libraries]
mapbox-android = { module = "com.mapbox.maps:android", version.ref = "mapbox" }
mapbox-compose = { module = "com.mapbox.extension:maps-compose", version.ref = "mapbox" }
sourceSets {
// ...

  androidMain.dependencies {
     implementation(libs.mapbox.android)
     implementation(libs.mapbox.compose)
  }
}

2.- Display Map

// commonMain

@Composable
expect fun MapView(
    geoJSON: String? = null,
)
// AndroidMain

@Composable
fun MapView(
    geoJSON: String? = null,
) {
    val mapViewportState = remember { MapViewportState() }
    MapboxMap(
        modifier = Modifier.fillMaxSize(),
        style = { MapboxStandardSatelliteStyle() }
    )
}

The code above receives a geoJSON string parameter, which represents whatever GeoJSON you want to show. Now If you run this on an Android device… *Voilà! *now you have a Globe!
Screenshot_20241205-123633

Displaying GeoJSON on the map


// AndroidMain

@Composable
fun MapView(
    geoJSON: String? = null,
) {
    val mapViewportState = remember { MapViewportState() }
    MapboxMap(
        modifier = Modifier.fillMaxSize(),
        style = { MapboxStandardSatelliteStyle() }
    ) {
        if (geoJSON != null) {
            val sourceState = rememberGeoJsonSourceState {
                data = GeoJSONData(geoJSON)
            }
            LineLayer(sourceState = sourceState) {
                lineColor = ColorValue(Color.Green)
                lineWidth = DoubleValue(2.0)
            }
        }
    }
}

So far we’ve gone almost 1-1 with the documentation, but as you might verify when you run this and update the geoJSON parameter the Layer won’t update!

Dynamically updating GeoJSON

To update the source, we’ll use MapEffect as a way to access the lower-level MapView instance.

const val LINE_LAYER = "line-layer"
const val GEOJSON_LAYER_SOURCE = "geojson-layer-source"

@Composable
fun MapView(
    geoJSON: String? = null,
) {
    val mapViewportState = remember { MapViewportState() }
    MapboxMap(
        modifier = Modifier.fillMaxSize(),
        style = { MapboxStandardSatelliteStyle() }
    ) {
		// Use a key for the recomposition
            MapEffect(geoJSON) { mapView ->

                if (geoJSON != null) {
                    if (mapView.mapboxMap.styleLayerExists(LINE_LAYER)) {
                        mapView.mapboxMap.removeStyleLayer(LINE_LAYER)
                        mapView.mapboxMap.removeStyleSource(GEOJSON_LAYER_SOURCE)
                    }

                    mapView.mapboxMap.getStyle { style ->
                        style.addSource(
                            geoJsonSource(GEOJSON_LAYER_SOURCE) {
                                data(geoJSON)
                            }
                        )

                        style.addLayer(
                            lineLayer(LINE_LAYER, GEOJSON_LAYER_SOURCE) {
                                lineColor("#00ff00")
                                lineWidth(3.0)
                            }
                        )
                    }
                }
            }

    }
}

The MapEffect will run every time the geoJSON parameter change, and it will check if there’s a layer already existing, if it does it will remove both the layer and its source.
Then it will go ahead and create a data source and a layer with that data source.

Hope this helps you!

image