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
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 |
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>"
}
}
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.
class MyApplication : Application {
override fun onCreate() {
super.onCreate()
initMapKit()
}
}
or other entry point, see MapKit official documentation
@main
struct iOSApp: App {
init() {
AppKt.doInitMapKit()
}
// Your code here
}
or other entry point, see MapKit official documentation
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.
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()
/* ... */
}
To show map in app you can use compose multiplatform or platform native view and convert it available in common module type.
With compose module
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(),
)
}
Without compose module
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.
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
.
Use with compose module
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(),
)
}
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
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.
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 */
/* ... */
}
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)
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 */
/* ... */
}
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.
NOTE: API marked as deprecated in official Yandex MapKit SDK does not supported
- Base map features
- Control map
- Map objects
- Image providing from common (compose-resources, moko-resources)
- User's location
- Full version of MapKit SDK
- @SuLG-ik – main developer. telegram: @vollllodya