Platform setup
Getting started picks one setup for each target. Read the section for a target when you need a different runtime, want to know its limits, or are preparing a release.
Android
Section titled “Android”Android apps require Android 7.0 (API 24) or later.
Android apps package one runtime. Each runtime renders with one graphics API:
| Graphics API | Runtime |
|---|---|
| OpenGL ES | maplibre-compose-runtime-opengl-android |
| Vulkan | maplibre-compose-runtime-vulkan-android |
We recommend Vulkan. Use OpenGL ES if your app needs to support devices without a Vulkan driver.
Both runtimes contain native code for the armeabi-v7a, arm64-v8a, and
x86_64 ABIs. They have none for x86, so 32-bit x86 emulators can’t show a
map.
iOS apps require iOS 15.5 or later.
iOS always renders with Metal, and the runtime comes with the library. The
library builds for devices (iosArm64) and Apple silicon simulators
(iosSimulatorArm64). It has no iosX64 target, so the simulator on Intel Macs
is not supported.
Add the system libraries to Other Linker Flags, as shown in Getting started.
Native macOS
Section titled “Native macOS”The macosArm64 target runs Compose without a JVM. Like iOS, it renders with
Metal and needs no runtime artifact. It is experimental in both Compose and this
library, and it runs only on Apple silicon.
Each window that shows a map needs
ProvideMapPresentationHost around its
content. Maps draw in a native view behind the window’s Compose content, so
they don’t support graphicsLayer effects such as scaling and rotation.
Desktop (JVM)
Section titled “Desktop (JVM)”Desktop apps require Java 25 or later and a runtime for each supported operating system and CPU. Each window that shows a map also needs a window host.
Choose a runtime
Section titled “Choose a runtime”Desktop apps package one runtime for each operating system and CPU they ship to.
| Platform | Metal | Vulkan | OpenGL |
|---|---|---|---|
| Linux x64 | — | maplibre-compose-runtime-vulkan-linux-x64 | maplibre-compose-runtime-opengl-linux-x64 |
| Linux arm64 | — | maplibre-compose-runtime-vulkan-linux-arm64 | maplibre-compose-runtime-opengl-linux-arm64 |
| macOS arm64 | maplibre-compose-runtime-metal-macos-arm64 | maplibre-compose-runtime-vulkan-macos-arm64 | maplibre-compose-runtime-opengl-macos-arm64 |
| Windows x64 | — | maplibre-compose-runtime-vulkan-windows-x64 | maplibre-compose-runtime-opengl-windows-x64 |
| Windows arm64 | — | maplibre-compose-runtime-vulkan-windows-arm64 | maplibre-compose-runtime-opengl-windows-arm64 |
We recommend Metal on macOS and Vulkan on Linux and Windows. The demo app packages these by default. Intel Macs are not supported.
For local development and Compose Desktop installers, select the runtime for the machine running Gradle. Build each installer on its target operating system. This example selects the recommended backend and uses the same version as the library:
val osName = providers.systemProperty("os.name").get()val archName = providers.systemProperty("os.arch").get()val architecture = when (archName.lowercase()) { "x86_64", "amd64" -> "x64" "aarch64", "arm64" -> "arm64" else -> error("Unsupported desktop architecture: $archName")}val runtime = when { osName.startsWith("Mac", ignoreCase = true) && architecture == "arm64" -> "metal-macos-arm64" osName.startsWith("Linux", ignoreCase = true) -> "vulkan-linux-$architecture" osName.startsWith("Windows", ignoreCase = true) -> "vulkan-windows-$architecture" else -> error("Unsupported desktop platform: $osName $archName")}
kotlin { sourceSets { jvmMain.dependencies { runtimeOnly("org.maplibre.compose:maplibre-compose-runtime-$runtime:0.20.0") } }}The OpenGL runtimes need more from the graphics driver:
- On Linux and Windows, OpenGL must be able to share GPU memory with other
graphics APIs (the
GL_EXT_memory_objectextensions). - On Linux, the machine also needs a Vulkan driver, because the library uses Vulkan to allocate the memory that the map and Compose share.
Java and native access
Section titled “Java and native access”Desktop apps run on Java 25 or later. The map engine calls native code through
Java’s foreign function API, so every way you start the app must pass
--enable-native-access=ALL-UNNAMED to the JVM. Without it, the map fails to
load. Getting started shows
how to add the flag.
Window hosts
Section titled “Window hosts”Maps draw with the GPU context of the window that shows them. A
ComposeMapPresentationHost gives maps that context, and
ProvideMapPresentationHost installs it
for a window’s content. Each window that shows a map needs a host.
Maps also deliver their events on Dispatchers.Main, so the window toolkit must
supply a main dispatcher. Without one, the map runtime fails to start.
Nucleus
Section titled “Nucleus”We recommend Nucleus with its Tao backend. Its windows integrate more fully with each platform than AWT windows, which makes map gestures work better, and on Linux it supports Wayland and fractional scaling. Add its application and Tao window modules:
jvmMain.dependencies { implementation("dev.nucleusframework:nucleus.nucleus-application:3.0.0-dev-202610110853") implementation("dev.nucleusframework:nucleus.decorated-window-tao:3.0.0-dev-202610110853")}Start the app with nucleusApplication. Tao
supplies the main dispatcher. Copy the demo’s Tao host into your
app; it builds a host from the window’s Tao GPU context. Then wrap each window’s
content:
val host = rememberTaoComposeMapPresentationHost() ?: return@WindowScaffoldProvideMapPresentationHost(host = host) { DemoApp(state, launch, chromePadding) }Use the Nucleus Gradle plugin to configure the launcher. It
enables native access for ordinary launches and configures the entry point for
Compose Hot Reload. On macOS, add -XstartOnFirstThread to
nucleus.application.jvmArgs to work around
Tao’s dispatcher thread detection bug.
Hot reload also
needs --enable-native-access=ALL-UNNAMED. The Nucleus demo
shows the Gradle configuration and a complete app.
Compose Desktop (AWT)
Section titled “Compose Desktop (AWT)”For a Compose window that uses AWT, such as one from singleWindowApplication
or Window, use rememberAwtComposeMapPresentationHost, and add
kotlinx-coroutines-swing for the main dispatcher.
Getting started shows the
setup.
Other windows
Section titled “Other windows”A window that draws Compose another way creates its host with a factory on
ComposeMapPresentationHost: macosMetal, windowsDirect3d12,
linuxOpenGl, or windowsAngle. Pick the one that matches the window’s
operating system and graphics API. You supply the window’s Skia context and a
way to run code on its GPU thread:
@OptIn(ExperimentalMaplibreComposeApi::class)fun customMetalHost( gpuContext: () -> MetalComposeGpuContext?, runOnGpuThread: (Runnable) -> Unit,): ComposeMapPresentationHost = ComposeMapPresentationHost.macosMetal( description = "my Metal window", gpuContext = gpuContext, runOnGpuThread = runOnGpuThread, )These factories take types from Skiko, the Skia library that Compose draws
with. Those types may change in any minor release, so the factories require
opt-in to ExperimentalMaplibreComposeApi. The API reference
describes what each callback must do.
If the toolkit has no Dispatchers.Main, set mainDispatcher in
DefaultMapRuntime.configure to a single-threaded dispatcher for the
window’s thread.
Signed packages
Section titled “Signed packages”Signing a macOS app re-signs LWJGL’s native libraries and changes their bytes.
LWJGL then prints Incompatible Java and native library versions detected,
even though the libraries load and the map works. To turn off this check, add
-Dorg.lwjgl.util.NoHashChecks=true to the JVM arguments:
compose.desktop { application { jvmArgs += "-Dorg.lwjgl.util.NoHashChecks=true" }}The library supports Kotlin/JS. It has no Kotlin/Wasm target.
The library imports MapLibre GL JS as an ES module, so the JS target must build
ES modules with useEsModules(). The library includes its own build of MapLibre
GL JS; you don’t add it from npm.
Call installMaplibreCompose inside onWasmReady, before
ComposeViewport, as shown in
Getting started.
Use a development snapshot
Section titled “Use a development snapshot”Builds of main are published to Central Portal Snapshots. This
site documents the latest release. If you use a snapshot, read the
documentation for main
instead.
Add the snapshot repository:
dependencyResolutionManagement { repositories { maven { name = "Central Portal Snapshots" url = uri("https://central.sonatype.com/repository/maven-snapshots/") mavenContent { snapshotsOnly() } content { includeGroup("org.maplibre.compose") includeGroup("org.maplibre.nativeffi") } } }}repositories: - id: central-portal-snapshots url: https://central.sonatype.com/repository/maven-snapshots/Then set the version to v0.20.1-SNAPSHOT, for the library and for every runtime artifact:
[libraries]maplibre-compose = { module = "org.maplibre.compose:maplibre-compose", version = "0.20.1-SNAPSHOT" }