Skip to content

Quick start

1. Set the API key

The key is set once, before anything else touches MapKit. Get it from the official documentation.

fun initMapKit() {
    MapKit.setApiKey("<API_KEY>")
}

Keeping the key out of the sources

BuildKonfig generates it into common code at build time; this is what the sample does.

Call it from the entry point of each platform.

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        initMapKit()
    }
}
@main
struct iOSApp: App {
    init() {
        AppKt.doInitMapKit()
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

Any other entry point works too — see the official Android and iOS guides.

2. Initialize MapKit

On Android MapKit needs a Context and has to be told when the application comes to the foreground; on iOS neither is required. There are three ways to arrange that, and they can be mixed as long as nothing is done twice.

From Android sources

MapKit.initialize(Context) exists in androidMain only. The lifecycle calls go with it.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        MapKit.initialize(this)
    }

    override fun onStart() {
        super.onStart()
        MapKit.getInstance().onStart()
    }

    override fun onStop() {
        super.onStop()
        MapKit.getInstance().onStop()
    }
}

From common code, with Compose

Requires yandex-mapkit-kmp-compose

rememberAndInitializeMapKit() does the Android-only initialization and returns the instance; bindToLifecycleOwner() calls onStart() and onStop() for you, and onStop() again when the composable leaves the composition.

@Composable
fun MapScreen() {
    rememberAndInitializeMapKit().bindToLifecycleOwner()
    /* ... */
}

Initialize once

rememberAndInitializeMapKit() initializes MapKit on the first composition. If something already did that — MapKit.initialize(Context) in an Activity, for example — use rememberMapKit() instead, which only returns the instance.

Note also that disposing the composition that owns bindToLifecycleOwner() stops MapKit.

Around a view you already have

Every wrapped native type has a toCommon() extension in the platform source set, so a MapView created the way the official SDK does it can be handed to common code: MapView.toCommon() / YMKMapView.toCommon(), MapWindow.toCommon() / YMKMapWindow.toCommon(), Map.toCommon() / YMKMap.toCommon(). The opposite direction is toNative(), also platform-only.

This is the path described in Wrapper overview.

3. Show a map

private val moscow = CameraPosition(
    target = Point(55.751225, 37.629540),
    zoom = 15f,
    azimuth = 0f,
    tilt = 0f,
)

@Composable
fun MapScreen() {
    rememberAndInitializeMapKit().bindToLifecycleOwner()
    val cameraPositionState = rememberCameraPositionState { position = moscow }
    YandexMap(
        cameraPositionState = cameraPositionState,
        modifier = Modifier.fillMaxSize(),
    )
}

In commonMain:

fun setupMap(map: Map) {
    map.move(
        CameraPosition(Point(55.751225, 37.629540), zoom = 15f, azimuth = 0f, tilt = 0f),
    )
}

In androidMain:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        MapKit.initialize(this)
        setContentView(R.layout.activity_main)
        val mapView = findViewById<com.yandex.mapkit.mapview.MapView>(R.id.map)
        setupMap(mapView.mapWindow.map.toCommon())
    }
}

4. Add a placemark

@Composable
fun MapScreen() {
    rememberAndInitializeMapKit().bindToLifecycleOwner()
    val cameraPositionState = rememberCameraPositionState { position = moscow }
    YandexMap(
        cameraPositionState = cameraPositionState,
        modifier = Modifier.fillMaxSize(),
    ) {
        Placemark(
            state = rememberPlacemarkState(Point(55.751225, 37.629540)),
            icon = imageProvider(Res.drawable.pin_red),
            onTap = { point ->
                println("tapped at $point")
                true
            },
        )
    }
}
fun setupMap(map: Map, icon: ImageProvider) {
    map.mapObjects.addPlacemark().apply {
        geometry = Point(55.751225, 37.629540)
        setIcon(icon)
    }
}

ImageProvider is built in platform code — see Image resources.

5. React to events

Listeners are not retained by MapKit, so every subscription in this wrapper takes a WeakRef<Listener> and the caller keeps the strong reference.

YandexMap(modifier = Modifier.fillMaxSize()) {
    MapListeners(
        onMapTap = { point -> println("tap at $point") },
        onMapLoaded = { statistics -> println("loaded in ${statistics.fullyLoaded}") },
    )
}
class MapController(private val map: Map) {

    private val inputListener = InputListener(
        onMapTap = { _, point -> println("tap at $point") },
        onMapLongTap = { _, point -> println("long tap at $point") },
    )

    init {
        map.addInputListener(inputListener.asWeakRef())
    }
}

Storing inputListener in a field is part of the pattern: a listener referenced only by the subscription is collected, and the subscription silently stops firing. See Wrapper overview.

Where to go next