Skip to content

Kotlin-first SDK for Yandex MapKit. It's API is similar to the Yandex MapKit SDK but also supports multiplatform projects and compose multiplaform, enabling you to use MapKit directly from your common source targeting iOS or Android.

License

Notifications You must be signed in to change notification settings

SuLG-ik/yandex-mapkit-kmp

Repository files navigation

Yandex MapKit KMP SDK

Kotlin Compose Multiplatform Maven Central License badge-android badge-ios

Kotlin-first SDK for Yandex MapKit. It's API is similar to the Yandex MapKit SDK but also supports multiplatform projects and compose multiplaform, enabling you to use MapKit directly from your common source targeting iOS or Android.

NOTE: It is not Yandex's project. Author has no connection with original SDK, it is wrapper above official Yandex MapKit SDK

Available libraries

The following libraries are available. It uses Yandex MapKit SDK version 4.6.1-lite

Module Gradle Dependency Description
Core ru.sulgik.mapkit:yandex-mapkit-kmp:0.0.2 Features of original Yandex MapKit SDK
Compose ru.sulgik.mapkit:yandex-mapkit-kmp-compose:0.0.2 Component to draw map and compose-resources usage as map images
Moko ru.sulgik.mapkit:yandex-mapkit-kmp-moko:0.0.2 Use moko-resources as map images. Requires native initialization
Moko Compose ru.sulgik.mapkit:yandex-mapkit-kmp-moko-compose:0.0.2 Use moko-resources as image provider. Not require native initialization

Installation

The minimum supported Android SDK is 24 (Android 7.0).

All modules are available for use in common code, but native API available only in native code.

commonMain.dependencies {
    //Core module
    implementation("ru.sulgik.mapkit:yandex-mapkit-kmp:<version>")

    // Optional modules
    implementation("ru.sulgik.mapkit:yandex-mapkit-kmp-<module>:<version>")
}

On iOS the official Yandex MapKit SDK in not linked as a transtive dependency. Therefore, any project using this SDK needs to link the same Yandex MapKit SDK as well. This can be done through your preferred installation method (Cocoapods/SPM).

Similarly, tests require linking as well. Make sure to add the required frameworks to the search path of your test targets. This can be done by specifying a cocoapods block in your build.gradle See actual version

cocoapods {
    pod("YandexMapsMobile") {
        version = "<version>"
    }
}

Setup MapKit

Add initializing MapKit with API key in common module. You can use BuildKonfig to provide API key during building

// In common module
fun initMapKit() {
    MapKit.setApiKey("<API_KEY>")
}

And call this function from entry point of your platform.

Android module:

class MyApplication : Application {
    override fun onCreate() {
        super.onCreate()
        initMapKit()
    }
}

or other entry point, see MapKit official documentation

IOS module:

@main
struct iOSApp: App {
    init() {
        AppKt.doInitMapKit()
    }
    // Your code here   
}

or other entry point, see MapKit official documentation

Initialize MapKit

MapKit, by and large, must be initialized via native library loading and lifecycle binding. There two ways to initialize. You can mix methods, but be careful to not repeat completed actions.

First method: in native android module

Call MapKit.initialize(Context) in your activity in android module and bind lifecycle.

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

}

Second method: in common code, requires compose module

Call 'rememberAndInitializeMapKit()' and call MapKit.bindToLifecycleOwner() in your compose screen.

IMPORTANT: MapKit.rememberAndInitializeMapKit() is difficult operation, use MapKit.rememberMapKit() if you already initialize MapKit via other method or early

NOTE: call MapKit.bindToLifecycleOwner() in composition context, which disposing means to stop MapKit

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

Setup view

To show map in app you can use compose multiplatform or platform native view and convert it available in common module type.

Create YandexMapController and pass it into YandexMap. Don't save instance of YandexMapController anywhere that has longer lifecycle than YandexMap, it contains native realization of view creating.

@Composable
fun MyMap() {
    val mapController = rememberMapControoller() // use to get MapWindow instance
    YandexMap(
        controller = mapController,
        modifier = Modifier.fillMaxSize(),
    )
}

All supported native types have extension functions (<NativeType>.toCommon(): <CommonType>), use it to pass exists native MapView to common code like: MapWindow.toCommon()/YMKWindow.toCommon(), Map.toCommon()/YMKMap.toCommon() and etc. Follow official documentation to setup native view.

Usage

Current API naming has same functions and types names as official API from Android Yandex MapKit SDK but with different package. Example com.yandex.mapkit.MapKit -> ru.sulgik.mapkit.MapKit, and com.yandex.mapkit.map.Map -> ru.sulgik.mapkit.map.Map.

MapView with some random generated placemarks. For more info about image provider follow here

fun MyMap() {
    rememberAndInitializeMapKit().bindToLifecycleOwner()
    val mapController = rememberYandexMapController()
    val map = mapController.mapWindow.map
    val placemarks = remember { randomPlacemarks() }

    val clusterImage = TODO("Your image loaded via provider")
    val pinRedImage = TODO("Your image loaded via provider")
    val pinGreenImage = TODO("Your image loaded via provider")
    val pinYellowImage = TODO("Your image loaded via provider")

    val clusterListener = remember(clusterImage) {
        ClusterListener { cluster ->
            cluster.appearance.setIcon(clusterImage)
            cluster.appearance.zIndex = 100f
        }
    }


    LaunchedEffect(map) {
        map.move(startPosition)
    }

    LaunchedEffect(map) {
        val typeToImageMap = mapOf(
            PlacemarkType.YELLOW to pinYellowImage,
            PlacemarkType.RED to pinRedImage,
            PlacemarkType.GREEN to pinGreenImage
        )
        val cluster =
            map.mapObjects.addClusterizedPlacemarkCollection(clusterListener)

        placemarks.forEach { (point, data) ->
            cluster.addPlacemark().apply {
                geometry = point
                setIcon(typeToImageMap[data.type]!!)
            }
        }
        cluster.clusterPlacemarks(60.0, 15)
    }
    YandexMap(
        controller = mapController,
        modifier = Modifier.fillMaxSize(),
    )
}

Image providing

Native API to set images for Android and iOS is different, library commonize this api with 4 variants of provide image.

NOTE: Use only raster images. SVG or XML vector are not supported by Yandex MapKit SDK

With core module

Use native image for Android Bitmap.toImageProvider(), ImageProvider.fromAsset(), ImageProvider.fromResource(), ImageProvider.fromFile() and iOS UIImage.toImageProvider() and convert it to ImageProvder which available in common module.

With compose module

It supports compose-resources to get ImageProvider.

// Common module:
fun MyMap() {
    val clusterImage = imageProvider(Res.drawable.cluster)
    val pinRedImage = imageProvider(Res.drawable.pin_red)
    val pinGreenImage = imageProvider(Res.drawable.pin_green)
    val pinYellowImage = imageProvider(Res.drawable.pin_yellow)
    /* Your MapView setup */
    /* ... */
}

With moko module

It does not depend on compose and supports convert ImageResource to ImageProvider, but you should have implementation of MOKOImageLoader created in platform module AndroidMOKOImageLoader(context) and IOSMOKOImageLoader()

val imageLoader: ImageLoader = TODO("Your image loader implementation")
val image = imageLoader.fromResource(MR.images.your_image)

With moko compose module

It supports convert ImageResource to ImageProvider and provides rememberMOKOImageLoader(). You can use it in common module to provide images.

@Composable
fun MyMap() {
    val imageLoader: ImageLoader = rememberMOKOImageLoader()
    val image = remember { imageLoader.fromResource(MR.images.your_image) }
    /* Your MapView setup */
    /* ... */
}

Sample app

The sample project is a app that show some library usage flow. It is rewritten official yandex mapkit example. It has Android and IOS build, but all code is in common code.

What's completed

NOTE: API marked as deprecated in official Yandex MapKit SDK does not supported

Authors

About

Kotlin-first SDK for Yandex MapKit. It's API is similar to the Yandex MapKit SDK but also supports multiplatform projects and compose multiplaform, enabling you to use MapKit directly from your common source targeting iOS or Android.

Topics

Resources

License

Stars

Watchers

Forks

Languages