Skip to content
This site is a preview of the main branch. The published documentation is at maplibre.org.

Present outside Compose UI

MaplibreMap draws a map inside Compose UI. Some screens are drawn by another UI framework: an Android Auto car screen, a CarPlay screen, a UIKit or SwiftUI view, or a web page built with Compose HTML, Kobweb, or React. To show a map there, use a presentation. It draws the map into the surface, view, or HTML element that the other framework gives you.

You still write the map’s sources, layers, and camera code in Kotlin. Create the MapState with MapRuntime.createMapState, which takes the same base style and content block as rememberMapState, and close the state when you no longer need the map.

A presentation draws only the map. It has no gestures, buttons, or attribution:

AndroidMapPresentation draws into an Android Surface, such as the one that Android Auto gives a car app. Connect the host’s surface callbacks to the presentation:

SurfaceMapHost.kt
/** Call every method on the main thread. */
class SurfaceMapHost(context: Context, lifecycle: Lifecycle) : AutoCloseable {
val state =
DefaultMapRuntime.instance.createMapState(
baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty")
) {
// Sources and layers, as in rememberMapState
}
val presentation = AndroidMapPresentation(context, state, lifecycle)
private var binding: AndroidMapPresentation.SurfaceBinding? = null
fun onSurfaceAvailable(surface: Surface, width: Int, height: Int, density: Float) {
binding = presentation.attachSurface(surface, width, height, density)
}
fun onSurfaceChanged(width: Int, height: Int, density: Float) {
binding?.update(width, height, density)
}
fun onSurfaceDestroyed() {
binding?.close()
binding = null
}
fun onConfigurationChanged(configuration: Configuration) {
presentation.updateConfiguration(configuration)
}
override fun close() {
presentation.close()
state.close()
}
}

width and height are in physical pixels, and density is physical pixels per dp. The map stops drawing while lifecycle is below STARTED. Pass the host’s configuration changes, so your map content sees the current night mode and locale.

The Android Auto demo connects these calls to the car app library’s SurfaceCallback, including scroll and zoom gestures.

AppleMapPresentation draws the map, and MaplibreMapView is a UIKit view that shows it:

UiKitMapHost.kt
/** Create and use this host on the main thread. */
class UiKitMapHost : AutoCloseable {
val state =
DefaultMapRuntime.instance.createMapState(
baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty")
) {
// Sources and layers, as in rememberMapState
}
val presentation = AppleMapPresentation(state, isActive = false)
val view: UIView = MaplibreMapView(presentation)
fun onSceneActivationChanged(active: Boolean) {
presentation.isActive = active
}
override fun close() {
view.removeFromSuperview()
presentation.close()
state.close()
}
}

Add view to a UIKit view hierarchy or a CarPlay window, or return it from makeUIView in a SwiftUI UIViewRepresentable. Set isActive from the scene that shows the map, so the map stops drawing while that scene is in the background. A CarPlay screen has its own scene, separate from the phone’s.

The CarPlay demo creates a Kotlin host like this one from Swift. If you manage your own CAMetalLayer, use AppleMapPresentation.attachLayer instead of MaplibreMapView.

WebMapPresentation draws the map into an HTML element. Give the element a CSS width and height:

WebMapHost.kt
/** Give the container a CSS width and height. */
@OptIn(ExperimentalMaplibreComposeApi::class)
class WebMapHost(container: HTMLElement) : AutoCloseable {
val state =
DefaultMapRuntime.instance.createMapState(
baseStyle = BaseStyle.Uri("https://tiles.openfreemap.org/styles/liberty")
) {
// Sources and layers, as in rememberMapState
}
val presentation = WebMapPresentation(state)
private val binding = presentation.attachContainer(container)
override fun close() {
binding.close()
presentation.close()
state.close()
}
}

Create the host when your framework adds the element to the page, and close it before the element is removed. The ferry departures demo does this in Compose HTML.

If your Content Security Policy blocks workers from blob: URLs, see configureMaplibreWorker.