Перейти к содержанию

Быстрый старт

1. Задайте API-ключ

Ключ задаётся один раз, до того как что-либо ещё коснётся MapKit. Получить его можно по официальной инструкции.

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

Как не держать ключ в исходниках

BuildKonfig генерирует его в общий код на этапе сборки; так сделано в примере.

Вызовите эту функцию из точки входа каждой платформы.

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

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

Подойдёт и любая другая точка входа — см. официальные руководства для Android и iOS.

2. Инициализируйте MapKit

На Android MapKit нужен Context, и ему нужно сообщать, когда приложение выходит на передний план; на iOS не требуется ни то, ни другое. Устроить это можно тремя способами, и их можно смешивать — главное не делать одно и то же дважды.

Из Android-исходников

MapKit.initialize(Context) существует только в androidMain. Вызовы жизненного цикла идут рядом.

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()
    }
}

Из общего кода, через Compose

Требуется yandex-mapkit-kmp-compose

rememberAndInitializeMapKit() выполняет инициализацию, нужную только на Android, и возвращает экземпляр; bindToLifecycleOwner() сам зовёт onStart() и onStop(), а также onStop() ещё раз, когда composable покидает композицию.

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

Инициализируйте один раз

rememberAndInitializeMapKit() инициализирует MapKit при первой композиции. Если это уже сделано где-то ещё — например, через MapKit.initialize(Context) в Activity — используйте rememberMapKit(), который только возвращает экземпляр.

И учтите: уничтожение композиции, которой принадлежит bindToLifecycleOwner(), останавливает MapKit.

Вокруг уже существующей View

У каждого обёрнутого нативного типа есть расширение toCommon() в платформенных исходниках, поэтому MapView, созданный так же, как это делает официальный SDK, можно передать в общий код: MapView.toCommon() / YMKMapView.toCommon(), MapWindow.toCommon() / YMKMapWindow.toCommon(), Map.toCommon() / YMKMap.toCommon(). Обратное направление — toNative(), тоже только на платформе.

Этот путь описан в Обзоре обёртки.

3. Покажите карту

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(),
    )
}

В commonMain:

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

В 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. Добавьте метку

@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 собирается в платформенном коде — см. Изображения.

5. Реагируйте на события

MapKit не удерживает слушателей, поэтому каждая подписка в этой обёртке принимает WeakRef<Listener>, а сильную ссылку держит вызывающий код.

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())
    }
}

Хранить inputListener в поле — часть паттерна: слушатель, на который ссылается только подписка, будет собран сборщиком мусора, и подписка молча перестанет срабатывать. См. Слушатели и WeakRef.

Куда дальше