Skip to content

Tutorial

This tutorial walks you through how to use the MapConductor Android SDK to display a map, add markers and shapes, and handle user interactions.

  • Installing and configuring the MapConductor SDK
  • Displaying a map
  • Adding markers, circles, and polylines
  • Handling tap and click events
  • Controlling camera position
  • Switching map SDKs
  • Android Studio installed
  • Basic knowledge of Jetpack Compose
  • Basic Kotlin knowledge

Add MapConductor dependencies to your module-level build.gradle.kts or build.gradle:

dependencies {
val mapconductorVersion = "1.1.3"
// Use BOM to unify versions
implementation(platform("com.mapconductor:mapconductor-bom:$mapconductorVersion"))
// Core module (required)
implementation("com.mapconductor:core")
// If using Google Maps
implementation("com.mapconductor:for-googlemaps")
// Or if using Mapbox
// implementation("com.mapconductor:for-mapbox")
// Or if using HERE Maps
// implementation("com.mapconductor:for-here")
// Or if using ArcGIS
// implementation("com.mapconductor:for-arcgis")
// Or if using MapLibre
// implementation("com.mapconductor:for-maplibre")
}

Add the following configuration to build.gradle.kts or build.gradle:

plugin {
id("org.jetbrains.kotlin.plugin.compose:1.9.25")
id("org.jetbrains.kotlin.android:1.9.25")
}
android {
compileSdk = 35
defaultConfig {
minSdk = 26
targetSdk = 35
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion =
project.property("1.9.25").toString()
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}

Important: MapConductor provides a unified API layer on top of existing map SDKs. Before using MapConductor, you must set up each map SDK individually.

Each map SDK requires its own API keys, permissions, and configuration:

Let’s start with the simplest way to display a map - showing a map centered on Tokyo.

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.mapconductor.core.GeoPoint
import com.mapconductor.core.MapCameraPosition
import com.mapconductor.googlemaps.GoogleMapView
import com.mapconductor.googlemaps.rememberGoogleMapViewState
@Composable
fun MyFirstMap(modifier: Modifier = Modifier) {
// Tokyo coordinates
val tokyo = GeoPoint.fromLatLong(35.6812, 139.7671)
// Initial camera position
val initialCamera = MapCameraPosition(
position = tokyo,
zoom = 12
)
// State management
val mapViewState = rememberGoogleMapViewState(
cameraPosition = initialCamera
)
// Display map
GoogleMapView(
modifier = modifier,
state = mapViewState
)
}

Map Result Note: Demo styling is modified from the default for clarity

That’s it! Now you have a map centered on Tokyo displayed in your app.

Let’s add markers to the map.

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.mapconductor.core.*
import com.mapconductor.googlemaps.GoogleMapView
import com.mapconductor.googlemaps.rememberGoogleMapViewState
@Composable
fun MapWithMarkers(modifier: Modifier = Modifier) {
// Coordinates
val marker1 = GeoPoint.fromLatLong(35.6586, 139.7454)
val marker2 = GeoPoint.fromLatLong(35.7101, 139.8107)
// Initial position
val initialCamera = MapCameraPosition(
position = GeoPoint.fromLatLong(35.6586, 139.7454),
zoom = 11
)
val mapViewState = rememberGoogleMapViewState(
cameraPosition = initialCamera
)
val onMarkerClick: (MarkerState) -> Unit = { markerState ->
println("Handle marker click${markerState.extra}")
}
GoogleMapView(
modifier = modifier.fillMaxSize(),
state = mapViewState
) {
// Add markers
Marker(
position = marker1,
icon = DefaultIcon(label = "Tokyo Tower"),
extra = "marker1",
onClick = onMarkerClick
)
Marker(
position = marker2,
icon = DefaultIcon(label = "Sky Tree"),
extra = "marker2",
onClick = onMarkerClick
)
}
}

Map with Markers Note: Demo styling is modified from the default for clarity

You can further customize markers with colors and styling:

Marker(
position = GeoPoint.fromLatLong(35.6586, 139.7454),
icon = DefaultIcon(
label = "TT",
backgroundColor = Color.Red,
textColor = Color.White
),
extra = "tokyo_tower",
anchor = Offset(0.5f, 1.0f) // Anchor info
)

Let’s draw a circle around a marker.

import androidx.compose.ui.graphics.Color
GoogleMapView(
modifier = modifier.fillMaxSize(),
state = mapViewState
) {
// Marker
Marker(
position = GeoPoint.fromLatLong(35.6586, 139.7454),
icon = DefaultIcon(label = "TT"),
extra = "tokyo_tower"
)
// Draw circle
Circle(
center = GeoPoint.fromLatLong(35.6586, 139.7454),
radiusMeters = 1000, // Radius (meters)
strokeColor = Color.Blue,
strokeWidth = 3.dp, // Stroke width (dp)
fillColor = Color.Blue.copy(alpha = 0.2f)
)
}

Marker with Circle Note: Demo styling is modified from the default for clarity

Now let’s draw a line connecting two points.

GoogleMapView(
modifier = modifier.fillMaxSize(),
state = mapViewState
) {
// Markers
Marker(position = GeoPoint.fromLatLong(35.6586, 139.7454), icon = DefaultIcon(label = "TT"))
Marker(position = GeoPoint.fromLatLong(35.7101, 139.8107), icon = DefaultIcon(label = "ST"))
// Draw line
Polyline(
points = listOf(
GeoPoint.fromLatLong(35.6586, 139.7454),
GeoPoint.fromLatLong(35.7101, 139.8107)
),
strokeColor = Color.Red,
strokeWidth = 5.dp
)
}

Marker with Polyline Note: Demo styling is modified from the default for clarity

You can handle when users tap the map:

import androidx.compose.runtime.*
@Composable
fun InteractiveMap(modifier: Modifier = Modifier) {
var clickedPosition by remember { mutableStateOf<GeoPointInterface?>(null) }
val initialCamera = MapCameraPosition(
position = GeoPoint.fromLatLong(35.6812, 139.7671),
zoom = 12
)
val mapViewState = rememberGoogleMapViewState(
cameraPosition = initialCamera
)
GoogleMapView(
modifier = modifier.fillMaxSize(),
state = mapViewState,
onMapClick = { geoPoint ->
// Map clicked
clickedPosition = geoPoint
println("Click position${geoPoint.latitude}, ${geoPoint.longitude}")
}
) {
// Show marker
clickedPosition?.let { position ->
Marker(
position = position,
icon = DefaultIcon(label = "!"),
extra = "clicked_marker"
)
}
}
}

Note: Demo styling is modified from the default for clarity

Handle clicks on markers and trigger animations:

val marker1 = GeoPoint.fromLatLong(35.6586, 139.7454)
val marker2 = GeoPoint.fromLatLong(35.7101, 139.8107)
GoogleMapView(
modifier = modifier.fillMaxSize(),
state = mapViewState
) {
Marker(
position = marker1,
icon = DefaultIcon(label = "TT"),
extra = "marker1",
onClick = { markerState ->
// Handle marker click
markerState.animate(MarkerAnimation.Drop)
}
)
Marker(
position = marker2,
icon = DefaultIcon(label = "ST"),
extra = "marker2",
onClick = { markerState ->
// Handle marker click
markerState.animate(MarkerAnimation.Bounce)
}
)
}

Note: Demo styling is modified from the default for clarity

Use buttons to move the camera to different locations:

import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.rememberCoroutineScope
import kotlinx.coroutines.launch
@Composable
fun MapWithCameraControl(modifier: Modifier = Modifier) {
val marker1 = GeoPoint.fromLatLong(35.6586, 139.7454)
val marker2 = GeoPoint.fromLatLong(35.7101, 139.8107)
val mapViewState = rememberGoogleMapViewState(
cameraPosition = MapCameraPosition(
position = marker1,
zoom = 12.0
)
)
val scope = rememberCoroutineScope()
Column(modifier = modifier.fillMaxSize()) {
// Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(onClick = {
scope.launch {
mapViewState.moveCameraTo(
MapCameraPosition(
position = marker1,
zoom = 15
),
durationMillis = 1000
)
}
}) {
Text("Tokyo Tower")
}
Button(onClick = {
scope.launch {
mapViewState.moveCameraTo(
MapCameraPosition(
position = marker2,
zoom = 15
),
durationMillis = 1000
)
}
}) {
Text("Sky Tree")
}
}
// 地図
GoogleMapView(
modifier = Modifier.fillMaxSize(),
state = mapViewState
) {
Marker(position = marker1, icon = DefaultIcon(label = "TT"))
Marker(position = marker2, icon = DefaultIcon(label = "ST"))
}
}
}

Listen to camera movement events:

GoogleMapView(
modifier = modifier.fillMaxSize(),
state = mapViewState,
onCameraMoveStart = {
println("Camera move started")
},
onCameraMove = { cameraPosition ->
println("Camera position: ${cameraPosition.position.latitude}, ${cameraPosition.position.longitude}")
},
onCameraMoveEnd = {
println("Camera move ended")
}
) {
// Markers etc.
}

MapConductor’s biggest advantage is the ability to switch map providers with minimal code changes.

import com.mapconductor.googlemaps.GoogleMapView
import com.mapconductor.googlemaps.rememberGoogleMapViewState
@Composable
fun MyMap() {
val mapViewState = rememberGoogleMapViewState(
cameraPosition = initialCamera
)
GoogleMapView(
state = mapViewState
) {
// Markers and other components
}
}

Markers, circles, polylines, and other components work with exactly the same code!

Map SDKMapViewStateInterfaceMapView
Google MapsrememberGoogleMapViewStateGoogleMapView
MapboxrememberMapboxViewStateMapboxMapView
HERE MapsrememberHereMapViewStateHereMapView
ArcGISrememberArcGISMapViewStateArcGISMapView
MapLibrerememberMapLibreViewStateMapLibreMapView

Here’s the complete sample code combining everything you’ve learned:

import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.mapconductor.core.*
import com.mapconductor.googlemaps.GoogleMapView
import com.mapconductor.googlemaps.rememberGoogleMapViewState
import kotlinx.coroutines.launch
@Composable
fun CompleteSample(modifier: Modifier = Modifier) {
val context = LocalContext.current
// Position information
val marker1 = GeoPoint.fromLatLong(35.6586, 139.7454)
val marker2 = GeoPoint.fromLatLong(35.7101, 139.8107)
// Save clicked position
var clickedPosition by remember { mutableStateOf<GeoPointInterface?>(null) }
val initialCamera = MapCameraPosition(
position = GeoPoint.fromLatLong(35.6812, 139.7671),
zoom = 12.0
)
val mapViewState = rememberGoogleMapViewState(
cameraPosition = initialCamera
)
val scope = rememberCoroutineScope()
val onMarkerClick: (MarkerState) -> Unit = { markerState ->
Toast
.makeText(
context,
"clicked: ${markerState.extra}",
Toast.LENGTH_SHORT,
)
.show()
}
Column(modifier = modifier.fillMaxSize()) {
// Control buttons
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(onClick = {
scope.launch {
mapViewState.moveCameraTo(
MapCameraPosition(position = marker1, zoom = 15.0),
durationMillis = 1000
)
}
}) {
Text("TT")
}
Button(onClick = {
scope.launch {
mapViewState.moveCameraTo(
MapCameraPosition(position = marker2, zoom = 15.0),
durationMillis = 1000
)
}
}) {
Text("ST")
}
}
// Map
GoogleMapView(
modifier = Modifier.fillMaxSize(),
state = mapViewState,
onMapClick = { geoPoint ->
clickedPosition = geoPoint
}
) {
// Markers
Marker(
position = marker1,
icon = DefaultIcon(
label = "TT",
fillColor = Color.Red,
),
extra = "marker1",
onClick = onMarkerClick
)
Marker(
position = marker2,
icon = DefaultIcon(
label = "ST",
fillColor = Color.Blue
),
extra = "marker2",
onClick = onMarkerClick
)
// Marker for clicked position
clickedPosition?.let { position ->
Marker(
position = position,
icon = DefaultIcon(
label = "!",
fillColor = Color.Green
),
extra = "clicked",
onClick = onMarkerClick
)
}
// Circle
Circle(
center = marker1,
radiusMeters = 1000,
strokeColor = Color.Red,
fillColor = Color.Red.copy(alpha = 0.2f)
)
Circle(
center = marker2,
radiusMeters = 1000,
strokeColor = Color.Blue,
fillColor = Color.Blue.copy(alpha = 0.2f)
)
// Polyline
Polyline(
points = listOf(marker1, marker2),
strokeColor = Color.Magenta,
strokeWidth = 3.dp
)
}
}
}

You’ve learned the basics of the MapConductor SDK. To learn more, check out:

  • Verify your API key is configured correctly
  • Check your internet connection
  • Verify required permissions are added to AndroidManifest.xml
  • Verify minSdk is set to 26 or higher
  • Check that Kotlin and Compose versions are compatible
  • Verify dependencies are added correctly
  • Verify MarkerState.onClick is configured
  • Ensure markers have the extra property set