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.
Overview
Section titled “Overview”| Component | Purpose |
|---|---|
rememberDefaultLocationProvider | Creates the platform location provider, including its permission handling. |
rememberLocationState | Collects updates while enabled and lifecycle-active. |
rememberDefaultOrientationProvider | Provides device orientation data from sensors. |
LocationPuck | Visual indicator that displays the user’s current position and bearing on the map. |
LocationTrackingEffect | Applies 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.permissionif (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.
Bearing
Section titled “Bearing”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 mode | Camera behavior |
|---|---|
IGNORE | Keep the current camera bearing. |
ALWAYS_NORTH | Reset the camera to north. |
TRACK_COURSE | Rotate the camera with the user’s direction of movement. |
TRACK_ORIENTATION | Rotate the camera with the device orientation. |
TRACK_AUTOMATIC | Use the more accurate course or orientation measurement. |
Implementation
Section titled “Implementation”val cameraState = rememberCameraState()
val locationProvider = rememberDefaultLocationProvider()val orientationProvider = rememberDefaultOrientationProvider() // optional: get device orientation from sensorsval 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)) }}Customizing the puck
Section titled “Customizing the puck”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.
Custom providers
Section titled “Custom providers”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.
Platform options
Section titled “Platform options”Android
Section titled “Android”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:
[libraries]maplibre-locationRuntimeGms = { module = "org.maplibre.compose:location-runtime-gms", version = "0.14.0" }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:
dependencyResolutionManagement { repositories { google() mavenCentral() maven("https://developer.huawei.com/repo/") { content { includeGroupAndSubgroups("com.huawei") } } }}Add the module to the Android source set:
[libraries]maplibre-locationRuntimeHms = { module = "org.maplibre.compose:location-runtime-hms", version = "0.14.0" }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.
Linux desktop
Section titled “Linux desktop”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.
macOS desktop
Section titled “macOS desktop”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.
Windows desktop
Section titled “Windows desktop”The backend uses Windows Runtime geolocation and observes location access
changes from Windows Settings. It applies both minimumInterval and
minimumDistance to location updates.