Skip to content

Integrating location

The location module provides a cross-platform way to track and visualize user position in MapLibre Compose.

Location support is split into a few small pieces. A LocationProvider supplies foreground location updates and carries the location permission, and rememberLocationState collects them according to permission and lifecycle state. LocationPuck draws the latest fix, and LocationTrackingEffect can keep the camera in sync with location changes.

ComponentPurpose
rememberDefaultLocationProviderCreates the platform location provider, including its permission handling.
rememberLocationStateCollects updates while enabled and lifecycle-active.
rememberDefaultOrientationProviderProvides device orientation data from sensors.
LocationPuckVisual indicator that displays the user’s current position and bearing on the map.
LocationTrackingEffectApplies location changes to application or camera state.

Android and iOS provide default location and orientation providers. Web and desktop provide default location providers but no default orientation readings. LocationState.status reports unsupported or misconfigured location setup without throwing.

Each collector of LocationProvider.updates(request) starts an independent platform location request. Cancelling collection stops that request and unregisters its callbacks. rememberLocationState collects updates only while enabled is true, permission is granted, and the lifecycle is active.

The provider carries the permission: LocationState.permission mirrors LocationProvider.permission, and LocationState.requestPermission() delegates to LocationProvider.requestPermission(). The library never requests permission automatically. Call requestPermission when the application is ready to present platform permission UI. This may be during startup or in response to an action such as a button click:

if (locationState.permission !is LocationPermission.Granted) {
Button(onClick = locationState::requestPermission) {
Text("Use my location")
}
}

When permission is not granted, LocationPermission.NotGranted says which step comes next: explain first when shouldShowRationale is set (Android only), request when canRequest is not false, and send the user to the system settings otherwise. rememberSystemSettingsLauncher opens the settings screens on the platforms that have them.

val settings = rememberSystemSettingsLauncher()
val permission = locationState.permission
if (permission is LocationPermission.NotGranted) {
when {
permission.shouldShowRationale ->
LocationRationale(onAccept = locationState::requestPermission)
permission.canRequest != false ->
Button(onClick = locationState::requestPermission) { Text("Use my location") }
settings.canOpenApplicationSettings ->
Button(onClick = { settings.openApplicationSettings() }) { Text("Open settings") }
}
}

locationState.location is null until the first fix arrives, so the puck initially draws nothing. Afterward, the state retains the last fix when tracking stops or permission changes.

Location.course is the direction of movement reported by the location provider. Orientation.orientation is the direction the device is pointing. Use locationState.mostAccurateBearing() when either source is acceptable and the more accurate one should be used.

The puck bearing and camera bearing are separate: LocationPuck(bearing = ...) rotates only the puck indicator, while LocationTrackingEffect and updateCamera(updateBearing = ...) control camera rotation.

Bearing update modeCamera behavior
IGNOREKeep the current camera bearing.
ALWAYS_NORTHReset the camera to north.
TRACK_COURSERotate the camera with the user’s direction of movement.
TRACK_ORIENTATIONRotate the camera with the device orientation.
TRACK_AUTOMATICUse the more accurate course or orientation measurement.
val cameraState = rememberCameraState()
val locationProvider = rememberDefaultLocationProvider()
val orientationProvider =
rememberDefaultOrientationProvider() // optional: get device orientation from sensors
val locationState =
rememberLocationState(
provider = locationProvider,
orientationProvider = orientationProvider,
)
MaplibreMap(cameraState = cameraState) {
LocationPuck(
idPrefix = "user",
location = locationState.location,
// optional: combine course and orientation bearing
bearing = locationState.mostAccurateBearing(),
cameraState = cameraState,
)
LocationTrackingEffect(locationState = locationState) {
cameraState.animateTo(CameraPosition(target = currentLocation.position.value, zoom = 15.0))
}
}

Use accuracyThreshold = Float.POSITIVE_INFINITY to hide the accuracy circle. Use showBearing = false or showBearingAccuracy = false to hide bearing indicators. If you use the Material 3 extension module, pass colors = LocationPuckDefaults.colors() for themed colors. onClick and onLongClick can react to interactions with the puck.

A custom LocationProvider implements only updates(request). permission defaults to always granted and requestPermission() defaults to a no-op, so a source where permission is not a concept, such as an external receiver or a network feed, needs no permission handling.

A provider that wraps a real platform source can delegate permission to the building blocks that the default providers use: AndroidLocationPermissionRequester on Android, IosLocationPermissionRequester on iOS, BrowserLocationPermissionRequester on web, and MacosLocationPermissionRequester, LinuxPortalLocationPermissionRequester, or WindowsLocationPermissionRequester on desktop.

The default provider observes and requests Android’s runtime location permissions. Declare ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION in the application manifest. Declare ACCESS_BACKGROUND_LOCATION only when the application tracks location in the background.

The permission request launches the system dialog through the activity that the context resolves to, so it works from a directly constructed provider too; with a context that cannot reach an activity, the request does nothing and the reported permission stays accurate.

Choose one fused-location runtime module for automatic discovery. When both modules and services are present, automatic discovery selects Google Play services.

Add the Google Play services module to the Android source set:

libs.versions.toml
[libraries]
maplibre-locationRuntimeGms = { module = "org.maplibre.compose:location-runtime-gms", version = "0.14.0" }
build.gradle.kts
androidMain.dependencies {
implementation(libs.maplibre.locationRuntimeGms)
}

The default providers then use fused location and orientation on a device where Google Play services is available, and the framework providers otherwise. Construct FusedLocationProvider and FusedOrientationProvider directly when an application needs the fused providers regardless of discovery.

Huawei devices and devices with HMS Core can use the Huawei Mobile Services module. Complete Huawei’s HMS Core preparation, including the AppGallery Connect configuration file and Gradle plugin. Add Huawei’s repository to dependency resolution:

settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven("https://developer.huawei.com/repo/") {
content { includeGroupAndSubgroups("com.huawei") }
}
}
}

Add the module to the Android source set:

libs.versions.toml
[libraries]
maplibre-locationRuntimeHms = { module = "org.maplibre.compose:location-runtime-hms", version = "0.14.0" }
build.gradle.kts
androidMain.dependencies {
implementation(libs.maplibre.locationRuntimeHms)
}

The default location provider then uses HMS fused location when HMS Core is available. It requests WGS84 coordinates explicitly. Android’s framework provider continues to supply orientation. HMS Core supports Huawei devices with EMUI 5 or newer and non-Huawei devices with Android 5.1 or newer. Construct org.maplibre.compose.hms.FusedLocationProvider directly when an application needs the HMS provider regardless of discovery.

The default web provider uses navigator.geolocation. Browsers require a secure context, except for development origins such as localhost, and may also block location through the geolocation permissions policy. Calling requestPermission starts a one-shot request so that the browser can present its prompt; active tracking then uses a watch that is removed when collection stops.

The browser API accepts a high-accuracy preference but has no interval or distance thresholds. The provider applies minimumInterval after receiving fixes and ignores minimumDistance.

The backend connects to org.freedesktop.portal.Desktop on the D-Bus session bus. The active portal implementation supplies location data, commonly through GeoClue. A missing portal reports an unsupported backend through LocationState.status.

On the standard AWT map host, the portal attaches its permission dialog to that window. A custom X11 or Wayland host can expose its window through ComposeMapHost.xdgPortalWindow, and an application without a map host can install one with LocalXdgPortalWindow. Other setups use an unparented dialog.

Each desktop rendering runtime installs its matching backend. A missing backend reports an unsupported backend, and multiple backends report a configuration failure through LocationState.status.

The backend uses Core Location. Add the location usage description and location entitlement that Apple requires.

minimumInterval is ignored because Core Location filters only by distance.

The backend uses Windows Runtime geolocation and observes location access changes from Windows Settings. It applies both minimumInterval and minimumDistance to location updates.