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

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 apps require Android 7.0 (API 24) or later.

Android apps package one runtime. Each runtime renders with one graphics API:

Graphics APIRuntime
OpenGL ESmaplibre-compose-runtime-opengl-android
Vulkanmaplibre-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.

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 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.

Desktop apps package one runtime for each operating system and CPU they ship to.

PlatformMetalVulkanOpenGL
Linux x64—maplibre-compose-runtime-vulkan-linux-x64maplibre-compose-runtime-opengl-linux-x64
Linux arm64—maplibre-compose-runtime-vulkan-linux-arm64maplibre-compose-runtime-opengl-linux-arm64
macOS arm64maplibre-compose-runtime-metal-macos-arm64maplibre-compose-runtime-vulkan-macos-arm64maplibre-compose-runtime-opengl-macos-arm64
Windows x64—maplibre-compose-runtime-vulkan-windows-x64maplibre-compose-runtime-opengl-windows-x64
Windows arm64—maplibre-compose-runtime-vulkan-windows-arm64maplibre-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:

build.gradle.kts
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_object extensions).
  • On Linux, the machine also needs a Vulkan driver, because the library uses Vulkan to allocate the memory that the map and Compose share.

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.

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.

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:

build.gradle.kts
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@WindowScaffold
ProvideMapPresentationHost(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.

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.

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.

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:

build.gradle.kts
compose.desktop {
application {
jvmArgs += "-Dorg.lwjgl.util.NoHashChecks=true"
}
}

See LWJGL discussion #1007.

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.

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:

settings.gradle.kts
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")
}
}
}
}

Then set the version to v0.20.1-SNAPSHOT, for the library and for every runtime artifact:

libs.versions.toml
[libraries]
maplibre-compose = { module = "org.maplibre.compose:maplibre-compose", version = "0.20.1-SNAPSHOT" }