Skip to content

Image resources

Placemarks, clusters and polygon patterns need an image, and MapKit takes it as an ImageProvider. In common code ImageProvider is an opaque interface: commonMain has no Bitmap and no UIImage, so the provider is built on the platform side or by one of the resource modules.

Where the image comes from What to use
Android resources, assets, files, a Bitmap the androidMain factories below
A UIImage ImageProvider.fromUIImage
Compose Multiplatform resources imageProvider(Res.drawable.x)
Composable content imageProvider { }
moko-resources MOKOImageLoader

From platform code

Build the providers where the platform types are available and pass them into common code.

val fromDrawable = ImageProvider.fromResource(context, R.drawable.ic_cluster)
val fromAsset = ImageProvider.fromAsset(context, "cluster.png")
val fromFile = ImageProvider.fromFile("/path/to/cluster.png")
val fromBitmap = ImageProvider.fromBitmap(bitmap)

val alsoFromBitmap = bitmap.toImageProvider()

Every factory has an overload taking MapKit's caching parameters. fromBitmap needs an id too, because a bitmap has no natural one:

val cached = ImageProvider.fromResource(
    context = context,
    resourceId = R.drawable.ic_cluster,
    isCacheable = true,
)

val cachedBitmap = ImageProvider.fromBitmap(
    bitmap = bitmap,
    isCacheable = true,
    id = "cluster-24",
)

Caching is worth turning on

MapKit caches an image by its id and reuses the decoded copy for every placemark that shares it — the difference is visible with hundreds of markers.

val provider = ImageProvider.fromUIImage(uiImage)

iOS MapKit takes a plain UIImage, so there is no id and no caching parameter — the Android-only ones stay in androidMain.

Handing them over:

class MapIcons(
    val pin: ImageProvider,
    val cluster: ImageProvider,
)

fun setupMap(map: Map, icons: MapIcons) {
    map.mapObjects.addPlacemark().apply {
        geometry = Point(55.751225, 37.629540)
        setIcon(icons.pin)
    }
}
fun mapIcons(context: Context): MapIcons {
    return MapIcons(
        pin = ImageProvider.fromResource(context, R.drawable.pin),
        cluster = ImageProvider.fromResource(context, R.drawable.cluster),
    )
}
fun mapIcons(): MapIcons {
    return MapIcons(
        pin = ImageProvider.fromUIImage(UIImage.imageNamed("pin")!!),
        cluster = ImageProvider.fromUIImage(UIImage.imageNamed("cluster")!!),
    )
}

Animated images

AnimatedImageProvider feeds animated placemark icons and animated polygon patterns. Unlike ImageProvider, it can be built entirely from common code:

val fromData = AnimatedImageProvider.fromByteArray(bytes)
val fromFile = AnimatedImageProvider.fromFile(path)

val built = AnimatedImageProvider.fromAnimatedImage(
    AnimatedImage(loopCount = 0).apply {
        addFrame(frameOne, 100.milliseconds)
        addFrame(frameTwo, 100.milliseconds)
    },
)

The frames are ImageProviders, so they still come from the platform side. loopCount = 0 loops forever. Android additionally has AnimatedImageProvider.fromResource(context, id) and fromAsset(context, name).

moko-resources

Requires yandex-mapkit-kmp-moko

MOKOImageLoader turns a moko ImageResource into an ImageProvider. The Android implementation needs a Context, the iOS one does not, so the loader itself is created on the platform side and passed into common code.

fun setupMap(map: Map, imageLoader: MOKOImageLoader) {
    val cluster = imageLoader.fromResource(MR.images.cluster)
    val pin = imageLoader.fromResource(MR.images.pin, isCacheable = true)
}
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setupMap(map, AndroidMOKOImageLoader(this))
    }
}
fun setupMap(map: Map) {
    setupMap(map, IOSMOKOImageLoader())
}

In a composable context yandex-mapkit-kmp-moko-compose removes the platform step entirely — see Image resources in Compose.